diff --git a/cmd/server/main.go b/cmd/server/main.go index e325d5a..aed0cac 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,16 +1,73 @@ package main import ( + "crypto/md5" + "encoding/hex" + "fmt" "log" + "net/http" + "github.com/sentriz/gonic/context" "github.com/sentriz/gonic/db" "github.com/sentriz/gonic/handler" "github.com/sentriz/gonic/router" + "github.com/sentriz/gonic/subsonic" + + _ "github.com/jinzhu/gorm/dialects/sqlite" + "github.com/labstack/echo" ) +var ( + username = "senan" + password = "howdy" + requiredParameters = []string{ + "u", "t", "s", "v", "c", + } +) + +func checkCredentials(token, salt string) bool { + toHash := fmt.Sprintf("%s%s", password, salt) + hash := md5.Sum([]byte(toHash)) + expToken := hex.EncodeToString(hash[:]) + return token == expToken +} + +func contextMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + return next(&context.Subsonic{c}) + } +} + +func validationMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + cc := c.(*context.Subsonic) + for _, req := range requiredParameters { + param := cc.QueryParams().Get(req) + if param != "" { + continue + } + return cc.Respond(http.StatusBadRequest, subsonic.NewError( + 10, fmt.Sprintf("please provide a `%s` parameter", req), + )) + } + credsOk := checkCredentials( + cc.QueryParams().Get("t"), // token + cc.QueryParams().Get("s"), // salt + ) + if !credsOk { + return cc.Respond(http.StatusBadRequest, subsonic.NewError( + 40, "invalid username or password", + )) + } + return next(c) + } +} + func main() { d := db.New() r := router.New() + r.Use(contextMiddleware) + r.Use(validationMiddleware) h := &handler.Handler{ DB: d, Router: r, diff --git a/context/context.go b/context/context.go new file mode 100644 index 0000000..f8f5d2d --- /dev/null +++ b/context/context.go @@ -0,0 +1,24 @@ +package context + +import ( + "github.com/sentriz/gonic/subsonic" + + "github.com/labstack/echo" +) + +type Subsonic struct { + echo.Context +} + +func (c *Subsonic) Respond(code int, r *subsonic.Response) error { + format := c.QueryParams().Get("f") + switch format { + case "json": + return c.JSON(code, r) + case "jsonp": + callback := c.QueryParams().Get("callback") + return c.JSONP(code, callback, r) + default: + return c.XML(code, r) + } +} diff --git a/handler/article.go b/handler/article.go index ae027c7..30a113e 100644 --- a/handler/article.go +++ b/handler/article.go @@ -3,10 +3,15 @@ package handler import ( "net/http" + "github.com/sentriz/gonic/context" + "github.com/sentriz/gonic/subsonic" + "github.com/labstack/echo" ) // GetTest doesn't do anything func (h *Handler) GetTest(c echo.Context) error { - return c.JSON(http.StatusOK, "hello") + cc := c.(*context.Subsonic) + resp := subsonic.NewResponse() + return cc.Respond(http.StatusOK, resp) } diff --git a/subsonic/media.go b/subsonic/media.go new file mode 100644 index 0000000..ff5244f --- /dev/null +++ b/subsonic/media.go @@ -0,0 +1,94 @@ +package subsonic + +import "encoding/xml" + +type Album struct { + XMLName xml.Name `xml:"album" json:"-"` + Id uint64 `xml:"id,attr" json:"id"` + Name string `xml:"name,attr" json:"name"` + ArtistId uint64 `xml:"artistId,attr" json:"artistId"` + ArtistName string `xml:"artist,attr" json:"artist"` + SongCount uint64 `xml:"songCount,attr" json:"songCount"` + Duration uint64 `xml:"duration,attr" json:"duration"` + CoverArt string `xml:"coverArt,attr" json:"coverArt"` + Created string `xml:"created,attr" json:"created"` + Songs *[]*Song `xml:"song" json:"song,omitempty"` +} + +type RandomSongs struct { + XMLName xml.Name `xml:"randomSongs" json:"-"` + Songs []*Song `xml:"song" json:"song"` +} + +type Song struct { + XMLName xml.Name `xml:"song" json:"-"` + Id uint64 `xml:"id,attr" json:"id"` + Parent uint64 `xml:"parent,attr" json:"parent"` + Title string `xml:"title,attr" json:"title"` + Album string `xml:"album,attr" json:"album"` + Artist string `xml:"artist,attr" json:"artist"` + IsDir bool `xml:"isDir,attr" json:"isDir"` + CoverArt string `xml:"coverArt,attr" json:"coverArt"` + Created string `xml:"created,attr" json:"created"` + Duration uint64 `xml:"duration,attr" json:"duration"` + Genre string `xml:"genre,attr" json:"genre"` + BitRate uint64 `xml:"bitRate,attr" json:"bitRate"` + Size uint64 `xml:"size,attr" json:"size"` + Suffix string `xml:"suffix,attr" json:"suffix"` + ContentType string `xml:"contentType,attr" json:"contentType"` + IsVideo bool `xml:"isVideo,attr" json:"isVideo"` + Path string `xml:"path,attr" json:"path"` + AlbumId uint64 `xml:"albumId,attr" json:"albumId"` + ArtistId uint64 `xml:"artistId,attr" json:"artistId"` + TrackNo uint64 `xml:"track,attr" json:"track"` + Type string `xml:"type,attr" json:"type"` +} + +type Artist struct { + XMLName xml.Name `xml:"artist" json:"-"` + Id uint64 `xml:"id,attr" json:"id"` + Name string `xml:"name,attr" json:"name"` + CoverArt string `xml:"coverArt,attr" json:"coverArt"` + AlbumCount uint64 `xml:"albumCount,attr" json:"albumCount"` + Albums []*Album `xml:"album,omitempty" json:"album,omitempty"` +} + +type Indexes struct { + LastModified uint64 `xml:"lastModified,attr" json:"lastModified"` + Index *[]*Index `xml:"index" json:"index"` +} + +type Index struct { + XMLName xml.Name `xml:"index" json:"-"` + Name string `xml:"name,attr" json:"name"` + Artists []*Artist `xml:"artist" json:"artist"` +} + +type Directory struct { + XMLName xml.Name `xml:"directory" json:"-"` + Id string `xml:"id,attr" json:"id"` + Parent string `xml:"parent,attr" json:"parent"` + Name string `xml:"name,attr" json:"name"` + Starred string `xml:"starred,attr,omitempty" json:"starred,omitempty"` + Children []Child `xml:"child" json:"child"` +} + +type Child struct { + XMLName xml.Name `xml:child` + Id string `xml:"id,attr"` + Parent string `xml:"parent,attr"` + Title string `xml:"title,attr"` + IsDir bool `xml:"isDir,attr"` + Album string `xml:"album,attr,omitempty"` + Artist string `xml:"artist,attr,omitempty"` + Track uint64 `xml:"track,attr,omitempty"` + Year uint64 `xml:"year,attr,omitempty"` + Genre string `xml:"genre,attr,omitempty"` + CoverArt uint64 `xml:"coverart,attr"` + Size uint64 `xml:"size,attr,omitempty"` + ContentType string `xml:"contentType,attr,omitempty"` + Suffix string `xml:"suffix,attr,omitempty"` + Duration uint64 `xml:"duration,attr,omitempty"` + BitRate uint64 `xml:"bitRate,attr,omitempty"` + Path string `xml:"path,attr,omitempty"` +} diff --git a/subsonic/response.go b/subsonic/response.go new file mode 100644 index 0000000..202f90d --- /dev/null +++ b/subsonic/response.go @@ -0,0 +1,54 @@ +// from "sonicmonkey" by https://github.com/jeena/sonicmonkey/ + +package subsonic + +import ( + "encoding/xml" +) + +var ( + apiVersion = "1.10.0" + xmlns = "http://subsonic.org/restapi" +) + +type Response struct { + XMLName xml.Name `xml:"subsonic-response" json:"-"` + Status string `xml:"status,attr" json:"status"` + Version string `xml:"version,attr" json:"version"` + XMLNS string `xml:"xmlns,attr" json:"xmlns"` + Error *Error `xml:"error" json:"error,omitempty"` + AlbumList2 *[]*Album `xml:"albumList2>album" json:"album,omitempty"` + Album *Album `xml:"album" json:"album,omitempty"` + Song *Song `xml:"song" json:"song,omitempty"` + Indexes *Indexes `xml:"indexes" json:"indexes,omitempty"` + Artists *[]*Index `xml:"artists>index" json:"artists,omitempty"` + Artist *Artist `xml:"artist" json:"artist,omitempty"` + MusicDirectory *Directory `xml:"directory" json:"directory,omitempty"` + RandomSongs *RandomSongs `xml:"randomSongs" json:"randomSongs,omitempty"` +} + +type Error struct { + XMLName xml.Name `xml:"error" json:"-"` + Code uint64 `xml:"code,attr" json:"code"` + Message string `xml:"message,attr" json:"message"` +} + +func NewResponse() *Response { + return &Response{ + Status: "ok", + XMLNS: xmlns, + Version: apiVersion, + } +} + +func NewError(code uint64, message string) *Response { + return &Response{ + Status: "failed", + XMLNS: xmlns, + Version: apiVersion, + Error: &Error{ + Code: code, + Message: message, + }, + } +}