pls pull #1
13 changed files with 442 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -11,3 +11,6 @@
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
# Custom Blacklist
|
||||||
|
config.json
|
63
config/config.go
Normal file
63
config/config.go
Normal file
|
@ -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: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
14
features/channel/channel.go
Normal file
14
features/channel/channel.go
Normal file
|
@ -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"`
|
||||||
|
}
|
40
features/channel/handler.go
Normal file
40
features/channel/handler.go
Normal file
|
@ -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)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
14
features/channel/routes.go
Normal file
14
features/channel/routes.go
Normal file
|
@ -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
|
||||||
|
}
|
47
main.go
Normal file
47
main.go
Normal file
|
@ -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))
|
||||||
|
}
|
36
request/meta/meta.go
Normal file
36
request/meta/meta.go
Normal file
|
@ -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
|
||||||
|
}
|
10
request/routes.go
Normal file
10
request/routes.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
const (
|
||||||
|
GET = "GET"
|
||||||
|
PUT = "PUT"
|
||||||
|
POST = "POST"
|
||||||
|
DELETE = "DELETE"
|
||||||
|
LINK = "LINK"
|
||||||
|
UNLINK = "UNLINK"
|
||||||
|
)
|
21
response/error.go
Normal file
21
response/error.go
Normal file
|
@ -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"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
87
response/response.go
Normal file
87
response/response.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
24
service/channel.go
Normal file
24
service/channel.go
Normal file
|
@ -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
|
||||||
|
}
|
33
service/service.go
Normal file
33
service/service.go
Normal file
|
@ -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
|
||||||
|
}
|
49
stringer/stringer.go
Normal file
49
stringer/stringer.go
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue