Changed routing framework from mux to Gin.
Swagger doc is now included in the application. A fronted can now be hosted from the application.
This commit is contained in:
@@ -1,32 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.SetCorsAndNoCacheHeaders(&w, r)
|
||||
type Index struct {
|
||||
}
|
||||
|
||||
if r.URL.Path == "/version" {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
func NewIndex() *Index {
|
||||
return &Index{}
|
||||
}
|
||||
|
||||
history := server.GetVersionHistory()
|
||||
_ = json.NewEncoder(w).Encode(history)
|
||||
|
||||
} else if r.URL.Path == "/docs" {
|
||||
http.ServeFile(w, r, "./docs/swagger.yaml")
|
||||
|
||||
} else if r.URL.Path == "/" {
|
||||
_, err := fmt.Fprint(w, "Hello, World!!")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
func (i *Index) GetVersion(ctx *gin.Context) {
|
||||
versionHistory := server.GetVersionHistory()
|
||||
if versionHistory.Version == "" {
|
||||
helpers.NewError(ctx, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, versionHistory)
|
||||
}
|
||||
|
||||
func (i *Index) GetDBTest(ctx *gin.Context) {
|
||||
server.TestDB()
|
||||
ctx.JSON(http.StatusOK, "TestedDB")
|
||||
}
|
||||
|
||||
149
pkg/api/music.go
149
pkg/api/music.go
@@ -1,79 +1,90 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/models"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func MusicHandler(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.SetCorsAndNoCacheHeaders(&w, r)
|
||||
if r.URL.Path == "/music" && r.Method == http.MethodGet {
|
||||
song := r.URL.Query().Get("song")
|
||||
if song == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, err := fmt.Fprint(w, "song can't be empty")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
s := server.GetSong(song)
|
||||
helpers.SendSong(w, s)
|
||||
}
|
||||
} else if r.URL.Path == "/music/first" && r.Method == http.MethodGet {
|
||||
song := server.GetSoundCheckSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/reset" && r.Method == http.MethodGet {
|
||||
server.Reset()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
} else if r.URL.Path == "/music/rand" && r.Method == http.MethodGet {
|
||||
song := server.GetRandomSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/rand/low" && r.Method == http.MethodGet {
|
||||
chance := server.GetRandomSongLowChance()
|
||||
helpers.SendSong(w, chance)
|
||||
|
||||
} else if r.URL.Path == "/music/info" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetSongInfo())
|
||||
|
||||
} else if r.URL.Path == "/music/list" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetPlayedSongs())
|
||||
|
||||
} else if r.URL.Path == "/music/next" {
|
||||
song := server.GetNextSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/previous" {
|
||||
song := server.GetPreviousSong()
|
||||
helpers.SendSong(w, song)
|
||||
|
||||
} else if r.URL.Path == "/music/all" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetAllGames())
|
||||
|
||||
} else if r.URL.Path == "/music/all/random" && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(server.GetAllGamesRandom())
|
||||
|
||||
} else if r.URL.Path == "/music/played" && r.Method == http.MethodPut {
|
||||
var p models.Played
|
||||
err := json.NewDecoder(r.Body).Decode(&p)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
server.SetPlayed(p.Song)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else if r.URL.Path == "/music/addQue" && r.Method == http.MethodGet {
|
||||
server.AddLatestToQue()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
type Music struct {
|
||||
}
|
||||
|
||||
func NewMusic() *Music {
|
||||
return &Music{}
|
||||
}
|
||||
|
||||
func (m *Music) GetSong(ctx *gin.Context) {
|
||||
song := ctx.Query("song")
|
||||
if song == "" {
|
||||
ctx.String(http.StatusBadRequest, "song can't be empty")
|
||||
}
|
||||
s := server.GetSong(song)
|
||||
helpers.SendSong(ctx, s)
|
||||
}
|
||||
|
||||
func (m *Music) GetMusicFirst(ctx *gin.Context) {
|
||||
song := server.GetSoundCheckSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) ResetMusic(ctx *gin.Context) {
|
||||
server.Reset()
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *Music) GetRandomSong(ctx *gin.Context) {
|
||||
song := server.GetRandomSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetRandomSongLowChance(ctx *gin.Context) {
|
||||
song := server.GetRandomSongLowChance()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetSongInfo(ctx *gin.Context) {
|
||||
song := server.GetSongInfo()
|
||||
ctx.JSON(http.StatusOK, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetPlayedSongs(ctx *gin.Context) {
|
||||
songList := server.GetPlayedSongs()
|
||||
ctx.JSON(http.StatusOK, songList)
|
||||
}
|
||||
|
||||
func (m *Music) GetNextSong(ctx *gin.Context) {
|
||||
song := server.GetNextSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetPreviousSong(ctx *gin.Context) {
|
||||
song := server.GetPreviousSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetAllGames(ctx *gin.Context) {
|
||||
gameList := server.GetAllGames()
|
||||
ctx.JSON(http.StatusOK, gameList)
|
||||
}
|
||||
|
||||
func (m *Music) GetAllGamesRandom(ctx *gin.Context) {
|
||||
gameList := server.GetAllGamesRandom()
|
||||
ctx.JSON(http.StatusOK, gameList)
|
||||
}
|
||||
|
||||
func (m *Music) PutPlayed(ctx *gin.Context) {
|
||||
var played models.Played
|
||||
if err := ctx.ShouldBindJSON(&played); err != nil {
|
||||
helpers.NewError(ctx, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
server.SetPlayed(played.Song)
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *Music) AddLatestToQue(ctx *gin.Context) {
|
||||
server.AddLatestToQue()
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"music-server/pkg/helpers"
|
||||
"github.com/gin-gonic/gin"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func SyncHandler(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.SetCorsAndNoCacheHeaders(&w, r)
|
||||
if r.URL.Path == "/sync" {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
server.SyncGames()
|
||||
_, err := fmt.Fprint(w, "Games are synced")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
} else if r.URL.Path == "/sync/reset" {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
server.ResetDB()
|
||||
_, err := fmt.Fprint(w, "Games and songs are deleted from the database")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
type Sync struct {
|
||||
}
|
||||
|
||||
func NewSync() *Sync {
|
||||
return &Sync{}
|
||||
}
|
||||
|
||||
func (s *Sync) SyncGames(ctx *gin.Context) {
|
||||
server.SyncGames()
|
||||
ctx.JSON(http.StatusOK, "Games are synced")
|
||||
}
|
||||
|
||||
func (s *Sync) ResetGames(ctx *gin.Context) {
|
||||
server.ResetDB()
|
||||
ctx.JSON(http.StatusOK, "Games and songs are deleted from the database")
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"music-server/pkg/api"
|
||||
"music-server/pkg/db"
|
||||
"net/http"
|
||||
"music-server/pkg/helpers"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
@@ -48,24 +50,47 @@ func CloseDb() {
|
||||
defer db.CloseDb()
|
||||
}
|
||||
|
||||
func SetupRestServer() {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/sync", api.SyncHandler)
|
||||
r.HandleFunc("/sync/{func}", api.SyncHandler)
|
||||
r.HandleFunc("/music", api.MusicHandler)
|
||||
r.HandleFunc("/music/{func}", api.MusicHandler)
|
||||
r.HandleFunc("/music/{func}/{func2}", api.MusicHandler)
|
||||
r.HandleFunc("/{func}", api.IndexHandler)
|
||||
r.Handle("/", http.FileServer(http.FS(os.DirFS("frontend/dist"))))
|
||||
http.Handle("/", r)
|
||||
func SetupRestServer(frontend embed.FS, swagger embed.FS) {
|
||||
|
||||
router := gin.Default()
|
||||
router.Use(helpers.SetCorsAndNoCacheHeaders())
|
||||
|
||||
sync := api.NewSync()
|
||||
syncGroup := router.Group("/sync")
|
||||
{
|
||||
syncGroup.GET("", sync.SyncGames)
|
||||
syncGroup.GET("/reset", sync.ResetGames)
|
||||
}
|
||||
|
||||
music := api.NewMusic()
|
||||
musicGroup := router.Group("/music")
|
||||
{
|
||||
musicGroup.GET("", music.GetSong)
|
||||
musicGroup.GET("first", music.GetMusicFirst)
|
||||
musicGroup.GET("reset", music.ResetMusic)
|
||||
musicGroup.GET("rand", music.GetRandomSong)
|
||||
musicGroup.GET("rand/low", music.GetRandomSongLowChance)
|
||||
musicGroup.GET("info", music.GetSongInfo)
|
||||
musicGroup.GET("list", music.GetPlayedSongs)
|
||||
musicGroup.GET("next", music.GetNextSong)
|
||||
musicGroup.GET("previous", music.GetPreviousSong)
|
||||
musicGroup.GET("all", music.GetAllGames)
|
||||
musicGroup.GET("all/random", music.GetAllGamesRandom)
|
||||
musicGroup.PUT("played", music.PutPlayed)
|
||||
musicGroup.GET("addQue", music.AddLatestToQue)
|
||||
}
|
||||
|
||||
index := api.NewIndex()
|
||||
router.GET("/version", index.GetVersion)
|
||||
router.GET("/test", index.GetDBTest)
|
||||
router.StaticFS("/swagger", helpers.EmbedFolder(swagger, "swagger", false))
|
||||
router.Use(static.Serve("/", helpers.EmbedFolder(frontend, "frontend/dist", true)))
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
log.Printf("Defaulting to port %s", port)
|
||||
}
|
||||
|
||||
log.Printf("Listening on port %s", port)
|
||||
log.Printf("Open http://localhost:%s in the browser", port)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
|
||||
router.Run(fmt.Sprintf(":%s", port))
|
||||
}
|
||||
|
||||
16
pkg/helpers/error.go
Normal file
16
pkg/helpers/error.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package helpers
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func NewError(ctx *gin.Context, status int, err error) {
|
||||
er := HTTPError{
|
||||
Code: status,
|
||||
Message: err.Error(),
|
||||
}
|
||||
ctx.JSON(status, er)
|
||||
}
|
||||
|
||||
type HTTPError struct {
|
||||
Code int `json:"code" example:"400"`
|
||||
Message string `json:"message" example:"status bad request"`
|
||||
}
|
||||
@@ -1,46 +1,73 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetCorsAndNoCacheHeaders(w *http.ResponseWriter, r *http.Request) {
|
||||
var etagHeaders = []string{
|
||||
"ETag",
|
||||
"If-Modified-Since",
|
||||
"If-Match",
|
||||
"If-None-Match",
|
||||
"If-Range",
|
||||
"If-Unmodified-Since",
|
||||
}
|
||||
func SetCorsAndNoCacheHeaders() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
|
||||
(*w).Header().Set("Expires", "Tue, 03 Jul 2001 06:00:00 GMT")
|
||||
(*w).Header().Set("Last-Modified", time.Now().String()+" GMT")
|
||||
(*w).Header().Set("Cache-Control", "no-cache, no-store, private, max-age=0")
|
||||
(*w).Header().Set("Pragma", "no-cache")
|
||||
(*w).Header().Set("X-Accel-Expires", "0")
|
||||
(*w).Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
for _, v := range etagHeaders {
|
||||
if r.Header.Get(v) != "" {
|
||||
r.Header.Del(v)
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func SendSong(writer http.ResponseWriter, Filename string) {
|
||||
type embedFileSystem struct {
|
||||
http.FileSystem
|
||||
indexes bool
|
||||
}
|
||||
|
||||
func (e embedFileSystem) Exists(prefix string, path string) bool {
|
||||
f, err := e.Open(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if indexing is allowed
|
||||
s, _ := f.Stat()
|
||||
if s.IsDir() && !e.indexes {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func EmbedFolder(fsEmbed embed.FS, targetPath string, index bool) static.ServeFileSystem {
|
||||
subFS, err := fs.Sub(fsEmbed, targetPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return embedFileSystem{
|
||||
FileSystem: http.FS(subFS),
|
||||
indexes: index,
|
||||
}
|
||||
}
|
||||
|
||||
func SendSong(ctx *gin.Context, Filename string) {
|
||||
fmt.Println("Client requests: " + Filename)
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(Filename)
|
||||
if err != nil {
|
||||
//File not found, send 404
|
||||
http.Error(writer, "Song not found.", 404)
|
||||
http.Error(ctx.Writer, "Song not found.", 404)
|
||||
return
|
||||
}
|
||||
defer func(openFile *os.File) {
|
||||
@@ -63,12 +90,32 @@ func SendSong(writer http.ResponseWriter, Filename string) {
|
||||
|
||||
//Send the headers
|
||||
//writer.Header().Set("Content-Disposition", "attachment; filename="+Filename)
|
||||
writer.Header().Set("Content-Type", "audio/mpeg")
|
||||
writer.Header().Set("Content-Length", FileSize)
|
||||
ctx.Writer.Header().Set("Content-Type", "audio/mpeg")
|
||||
ctx.Writer.Header().Set("Content-Length", FileSize)
|
||||
ctx.Writer.Header().Set("Expires", "Tue, 03 Jul 2001 06:00:00 GMT")
|
||||
ctx.Writer.Header().Set("Last-Modified", time.Now().String()+" GMT")
|
||||
ctx.Writer.Header().Set("Cache-Control", "no-cache, no-store, private, max-age=0")
|
||||
ctx.Writer.Header().Set("Pragma", "no-cache")
|
||||
ctx.Writer.Header().Set("X-Accel-Expires", "0")
|
||||
|
||||
var etagHeaders = []string{
|
||||
"ETag",
|
||||
"If-Modified-Since",
|
||||
"If-Match",
|
||||
"If-None-Match",
|
||||
"If-Range",
|
||||
"If-Unmodified-Since",
|
||||
}
|
||||
|
||||
for _, v := range etagHeaders {
|
||||
if ctx.Request.Header.Get(v) != "" {
|
||||
ctx.Request.Header.Del(v)
|
||||
}
|
||||
}
|
||||
|
||||
//Send the file
|
||||
//We read 512 bytes from the file already, so we reset the offset back to 0
|
||||
_, _ = openFile.Seek(0, 0)
|
||||
_, _ = io.Copy(writer, openFile) //'Copy' the file to the client
|
||||
_, _ = io.Copy(ctx.Writer, openFile) //'Copy' the file to the client
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Played struct {
|
||||
Song int
|
||||
}
|
||||
|
||||
type VersionData struct {
|
||||
Version string `json:"version"`
|
||||
Changelog string `json:"changelog"`
|
||||
Version string `json:"version" example:"1.0.0swagger.yaml"`
|
||||
Changelog string `json:"changelog" example:"account name"`
|
||||
History []VersionData `json:"history"`
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,18 @@ import (
|
||||
"music-server/pkg/models"
|
||||
)
|
||||
|
||||
func GetVersionHistory() models.VersionData {
|
||||
func TestDB() {
|
||||
db.Testf()
|
||||
}
|
||||
|
||||
data := models.VersionData{Version: "2.3.0",
|
||||
Changelog: "Images should not be included in the database, removes songs where the path doesn't work.",
|
||||
func GetVersionHistory() models.VersionData {
|
||||
data := models.VersionData{Version: "3.0",
|
||||
Changelog: "Changed routing framework from mux to Gin. Swagger doc is now included in the application. A fronted can now be hosted from the application.",
|
||||
History: []models.VersionData{
|
||||
{
|
||||
Version: "2.3.0",
|
||||
Changelog: "Images should not be included in the database, removes songs where the path doesn't work.",
|
||||
},
|
||||
{
|
||||
Version: "2.2.0",
|
||||
Changelog: "Changed the structure of the whole application, should be no changes to functionality.",
|
||||
@@ -53,28 +59,7 @@ func GetVersionHistory() models.VersionData {
|
||||
Version: "2.0.0",
|
||||
Changelog: "Rebuilt the application in Go.",
|
||||
},
|
||||
{
|
||||
Version: "1.2.0",
|
||||
Changelog: "Made the /sync endpoint async. " +
|
||||
"Fixed bug where the game list wasn't reloaded when using /reset. " +
|
||||
"Fixed bug where the songNo showed in the list didn't match what should be sent.",
|
||||
},
|
||||
{
|
||||
Version: "1.1.0",
|
||||
Changelog: "Added sync endpoint, don't really trust it to 100%, would say beta. " +
|
||||
"Fixed bug with /next after /previous. Added /reset endpoint. " +
|
||||
"Added some info more to /info and /list.",
|
||||
},
|
||||
{
|
||||
Version: "1.0.0",
|
||||
Changelog: "Added swagger documentation. Created version 1.0.",
|
||||
},
|
||||
{
|
||||
Version: "0.5.5",
|
||||
Changelog: "Added increase played endpoint.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user