add some endpoints

This commit is contained in:
sentriz
2019-04-01 13:53:21 +01:00
parent b7c398f1eb
commit f5aa05abc3
18 changed files with 455 additions and 267 deletions

View File

@@ -1,17 +0,0 @@
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 {
cc := c.(*context.Subsonic)
resp := subsonic.NewResponse()
return cc.Respond(http.StatusOK, resp)
}

View File

@@ -1,13 +1,58 @@
package handler
import (
"encoding/json"
"encoding/xml"
"fmt"
"log"
"net/http"
"github.com/jinzhu/gorm"
"github.com/labstack/echo"
"github.com/sentriz/gonic/subsonic"
)
// Handler is passed to the handler functions so
// they can access the database
type Handler struct {
DB *gorm.DB
Router *echo.Echo
type Controller struct {
DB *gorm.DB
}
func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.Response) {
format := r.URL.Query().Get("f")
switch format {
case "json":
w.Header().Set("Content-Type", "application/json")
data, err := json.Marshal(sub)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
w.Write([]byte(`{"subsonic-response":`))
w.Write(data)
w.Write([]byte("}"))
case "jsonp":
w.Header().Set("Content-Type", "application/javascript")
data, err := json.Marshal(sub)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
callback := r.URL.Query().Get("callback")
w.Write([]byte(fmt.Sprintf("%s(", callback)))
w.Write(data)
w.Write([]byte(");"))
default:
w.Header().Set("Content-Type", "application/xml")
data, err := xml.Marshal(sub)
if err != nil {
log.Printf("could not marshall to xml: %v\n", err)
}
w.Write(data)
}
}
func respond(w http.ResponseWriter, r *http.Request, sub *subsonic.Response) {
respondRaw(w, r, http.StatusOK, sub)
}
func respondError(w http.ResponseWriter, r *http.Request, code uint64, message string) {
respondRaw(w, r, http.StatusBadRequest, subsonic.NewError(
code, message,
))
}

149
handler/media.go Normal file
View File

@@ -0,0 +1,149 @@
package handler
import (
"fmt"
"net/http"
"strconv"
"unicode"
"github.com/jinzhu/gorm"
"github.com/sentriz/gonic/db"
"github.com/sentriz/gonic/subsonic"
)
func (c *Controller) Ping(w http.ResponseWriter, req *http.Request) {
sub := subsonic.NewResponse()
respond(w, req, sub)
}
func (c *Controller) GetIndexes(w http.ResponseWriter, req *http.Request) {
var artists []db.Artist
c.DB.Find(&artists)
indexMap := make(map[byte]*subsonic.Index)
for _, artist := range artists {
first := artist.Name[0]
if !unicode.IsLetter(rune(first)) {
first = 0x23 // '#'
}
_, ok := indexMap[first]
if !ok {
indexMap[first] = &subsonic.Index{
Name: string(first),
Artists: []*subsonic.Artist{},
}
}
indexMap[first].Artists = append(
indexMap[first].Artists,
&subsonic.Artist{
ID: artist.ID,
Name: artist.Name,
},
)
}
indexes := []*subsonic.Index{}
for _, v := range indexMap {
indexes = append(indexes, v)
}
sub := subsonic.NewResponse()
sub.Indexes = &subsonic.Indexes{
Index: &indexes,
}
respond(w, req, sub)
}
func browseArtist(c *gorm.DB, artist *db.Artist) *subsonic.Directory {
var cover db.Cover
var dir subsonic.Directory
dir.Name = artist.Name
dir.ID = artist.ID
dir.Parent = 0
var albums []*db.Album
c.Model(artist).Related(&albums)
dir.Children = make([]subsonic.Child, len(albums))
for i, album := range albums {
c.Model(album).Related(&cover)
dir.Children[i] = subsonic.Child{
Artist: artist.Name,
ID: album.ID,
IsDir: true,
Parent: artist.ID,
Title: album.Title,
CoverArt: cover.ID,
}
cover = db.Cover{}
}
return &dir
}
func browseAlbum(c *gorm.DB, album *db.Album) *subsonic.Directory {
var artist db.Artist
c.Model(album).Related(&artist)
var tracks []*db.Track
c.Model(album).Related(&tracks)
var cover db.Cover
c.Model(album).Related(&cover)
var dir subsonic.Directory
dir.Name = album.Title
dir.ID = album.ID
dir.Parent = artist.ID
dir.Children = make([]subsonic.Child, len(tracks))
for i, track := range tracks {
dir.Children[i] = subsonic.Child{
ID: track.ID,
Title: track.Title,
Parent: album.ID,
Artist: artist.Name,
ArtistID: artist.ID,
Album: album.Title,
AlbumID: album.ID,
IsDir: false,
Path: track.Path,
CoverArt: cover.ID,
}
}
return &dir
}
func (c *Controller) GetMusicDirectory(w http.ResponseWriter, req *http.Request) {
idStr := req.URL.Query().Get("id")
if idStr == "" {
respondError(w, req, 10, "please provide an `id` parameter")
return
}
id, _ := strconv.Atoi(idStr)
sub := subsonic.NewResponse()
var artist db.Artist
c.DB.First(&artist, id)
if artist.ID != 0 {
sub.MusicDirectory = browseArtist(c.DB, &artist)
respond(w, req, sub)
return
}
var album db.Album
c.DB.First(&album, id)
if album.ID != 0 {
sub.MusicDirectory = browseAlbum(c.DB, &album)
respond(w, req, sub)
return
}
respondError(w, req,
70, fmt.Sprintf("directory with id `%d` was not found", id),
)
}
func (c *Controller) GetCoverArt(w http.ResponseWriter, req *http.Request) {
idStr := req.URL.Query().Get("id")
if idStr == "" {
respondError(w, req, 10, "please provide an `id` parameter")
return
}
id, _ := strconv.Atoi(idStr)
var cover db.Cover
c.DB.First(&cover, id)
w.Write(cover.Image)
}
func (c *Controller) GetMusicFolders(w http.ResponseWriter, req *http.Request) {}
func (c *Controller) GetPlaylists(w http.ResponseWriter, req *http.Request) {}
func (c *Controller) GetGenres(w http.ResponseWriter, req *http.Request) {}
func (c *Controller) GetPodcasts(w http.ResponseWriter, req *http.Request) {}

96
handler/middleware.go Normal file
View File

@@ -0,0 +1,96 @@
package handler
import (
"crypto/md5"
"encoding/hex"
"fmt"
"log"
"net/http"
"github.com/jinzhu/gorm"
"github.com/sentriz/gonic/db"
)
var requiredParameters = []string{
"u", "v", "c",
}
func checkCredentialsNewWay(password, token, salt string) bool {
toHash := fmt.Sprintf("%s%s", password, salt)
hash := md5.Sum([]byte(toHash))
expToken := hex.EncodeToString(hash[:])
return token == expToken
}
func checkCredentialsOldWay(password, givenPassword string) bool {
if givenPassword[:4] == "enc:" {
bytes, _ := hex.DecodeString(givenPassword[4:])
givenPassword = string(bytes)
}
return password == givenPassword
}
func (c *Controller) LogConnection(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("connection from %s", r.RemoteAddr)
next.ServeHTTP(w, r)
}
}
func (c *Controller) CheckParameters(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
for _, req := range requiredParameters {
param := r.URL.Query().Get(req)
if param != "" {
continue
}
respondError(w, r, 10, fmt.Sprintf("please provide a `%s` parameter", req))
return
}
username := r.URL.Query().Get("u")
password := r.URL.Query().Get("p")
token := r.URL.Query().Get("t")
salt := r.URL.Query().Get("s")
passwordAuth := token == "" && salt == ""
tokenAuth := password == ""
if tokenAuth == passwordAuth {
respondError(w, r, 10, "please provide parameters `t` and `s`, or just `p`")
return
}
user := db.User{
Username: username,
}
err := c.DB.Where(user).First(&user).Error
if gorm.IsRecordNotFoundError(err) {
respondError(w, r, 40, "invalid username")
return
}
var credsOk bool
if tokenAuth {
credsOk = checkCredentialsNewWay(user.Password, token, salt)
} else {
credsOk = checkCredentialsOldWay(user.Password, password)
}
if !credsOk {
respondError(w, r, 40, "invalid password")
return
}
next.ServeHTTP(w, r)
}
}
func (c *Controller) EnableCORS(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods",
"POST, GET, OPTIONS, PUT, DELETE",
)
w.Header().Set("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization",
)
if r.Method == "OPTIONS" {
return
}
next.ServeHTTP(w, r)
}
}