Refactor handlers and update changelog for 5.0.0-Beta
Build / build (push) Successful in 48s

- Split IndexHandler into HealthHandler, VersionHandler, and CharacterHandler
- Rename index.go to version.go in backend
- Change VersionData.Changelog from string to []string
- Add changelog entries for issues #16-#23
- Remove TestDB function and related code
- Fix import ordering in several files

Closes #21, #22
References #16, #17, #18, #19, #20, #23

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-06-08 20:08:06 +02:00
parent a446dad7b6
commit 8f8b555ea5
13 changed files with 318 additions and 255 deletions
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"os"
"strings"
"music-server/internal/logging"
"go.uber.org/zap"
"music-server/internal/logging"
)
func GetCharacterList() []string {
@@ -30,10 +30,10 @@ func GetCharacterList() []string {
func GetCharacter(character string) string {
charactersPath := os.Getenv("CHARACTERS_PATH")
logging.GetLogger().Debug("Getting character", zap.String("character", character), zap.String("path", charactersPath))
// Clean the path - remove trailing slashes and then add one for consistency
charactersPath = strings.TrimSuffix(charactersPath, "/")
charactersPath += "/"
logging.GetLogger().Debug("Getting character", zap.String("character", character), zap.String("path", charactersPath+character))
return charactersPath + character
}
-95
View File
@@ -1,95 +0,0 @@
package backend
import (
"music-server/internal/db"
)
func TestDB() {
db.Testf()
}
type VersionData struct {
Version string `json:"version" example:"1.0.0"`
Changelog string `json:"changelog" example:"account name"`
History []VersionData `json:"history"`
}
func GetVersionHistory() VersionData {
data := VersionData{Version: "4.5.0",
Changelog: "#1 - Created request to check newest version of the app\n" +
"#2 - Added request to download the newest version of the app\n" +
"#3 - Added request to check progress during sync\n" +
"#4 - Now blocking all request while sync is in progress\n" +
"#5 - Implemented ants for thread pooling\n" +
"#6 - Changed the sync request to now only start the sync",
History: []VersionData{
{
Version: "4.0.0",
Changelog: "Changed framework from gin to Echo\n" +
"Reorganized the code\n" +
"Implemented sqlc\n" +
"Added support to send character images from the server\n" +
"Added function to create a new database of no one exists",
},
{
Version: "3.2",
Changelog: "Upgraded Go version and the version of all dependencies. Fixed som more bugs.",
},
{
Version: "3.1",
Changelog: "Fixed some bugs with songs not found made the application crash. Now checking if song exists and if not, remove song from DB and find another one. Frontend is now decoupled from the backend.",
},
{
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.",
},
{
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.",
},
{
Version: "2.1.4",
Changelog: "Game list should now be sorted, a new endpoint with the game list in random order have been added.",
},
{
Version: "2.1.3",
Changelog: "Added a check to see if song exists before returning it, if not a new song will be picked up.",
},
{
Version: "2.1.2",
Changelog: "Added test server to swagger file.",
},
{
Version: "2.1.1",
Changelog: "Fixed bug where wrong song was showed as currently played.",
},
{
Version: "2.1.0",
Changelog: "Added /addQue to add the last received song to the songQue. " +
"Changed /rand and /rand/low to not add song to the que. " +
"Changed /next to not call /rand when the end of the que is reached, instead the last song in the que will be resent.",
},
{
Version: "2.0.3",
Changelog: "Another small change that should fix the caching problem.",
},
{
Version: "2.0.2",
Changelog: "Hopefully fixed the caching problem with random.",
},
{
Version: "2.0.1",
Changelog: "Fixed CORS",
},
{
Version: "2.0.0",
Changelog: "Rebuilt the application in Go.",
},
},
}
return data
}
+111
View File
@@ -0,0 +1,111 @@
package backend
type VersionData struct {
Version string `json:"version" example:"1.0.0"`
Changelog []string `json:"changelog" example:"[\"Initial release\",\"Bug fixes\"]"`
}
var data = []VersionData{
{
Version: "5.0.0-Beta",
Changelog: []string{
"#16 - Upgrade Echo framework from v4 to v5",
"#17 - Add Zap structured logging framework",
"#18 - Add OpenAPI/Swagger documentation",
"#19 - Replace Tailwind CSS with pure CSS",
"#20 - Change domain from sanplex.tech to sanplex.xyz",
"#21 - Refactor handlers into domain-specific files",
"#22 - Change VersionData Changelog from string to string array",
"#23 - Update all dependencies to latest versions",
},
},
{
Version: "4.5.0",
Changelog: []string{
"#1 - Created request to check newest version of the app",
"#2 - Added request to download the newest version of the app",
"#3 - Added request to check progress during sync",
"#4 - Now blocking all request while sync is in progress",
"#5 - Implemented ants for thread pooling",
"#6 - Changed the sync request to now only start the sync",
},
},
{
Version: "4.0.0",
Changelog: []string{
"Changed framework from gin to Echo",
"Reorganized the code",
"Implemented sqlc",
"Added support to send character images from the server",
"Added function to create a new database of no one exists",
},
},
{
Version: "3.2",
Changelog: []string{"Upgraded Go version and the version of all dependencies. Fixed som more bugs."},
},
{
Version: "3.1",
Changelog: []string{"Fixed some bugs with songs not found made the application crash. Now checking if song exists and if not, remove song from DB and find another one. Frontend is now decoupled from the backend."},
},
{
Version: "3.0",
Changelog: []string{"Changed routing framework from mux to Gin. Swagger doc is now included in the application. A fronted can now be hosted from the application."},
},
{
Version: "2.3.0",
Changelog: []string{"Images should not be included in the database, removes songs where the path doesn't work."},
},
{
Version: "2.2.0",
Changelog: []string{"Changed the structure of the whole application, should be no changes to functionality."},
},
{
Version: "2.1.4",
Changelog: []string{"Game list should now be sorted, a new endpoint with the game list in random order have been added."},
},
{
Version: "2.1.3",
Changelog: []string{"Added a check to see if song exists before returning it, if not a new song will be picked up."},
},
{
Version: "2.1.2",
Changelog: []string{"Added test server to swagger file."},
},
{
Version: "2.1.1",
Changelog: []string{"Fixed bug where wrong song was showed as currently played."},
},
{
Version: "2.1.0",
Changelog: []string{
"Added /addQue to add the last received song to the songQue.",
"Changed /rand and /rand/low to not add song to the que.",
"Changed /next to not call /rand when the end of the que is reached, instead the last song in the que will be resent.",
},
},
{
Version: "2.0.3",
Changelog: []string{"Another small change that should fix the caching problem."},
},
{
Version: "2.0.2",
Changelog: []string{"Hopefully fixed the caching problem with random."},
},
{
Version: "2.0.1",
Changelog: []string{"Fixed CORS"},
},
{
Version: "2.0.0",
Changelog: []string{"Rebuilt the application in Go."},
},
}
func GetLatestVersion() VersionData {
return data[0]
}
func GetVersionHistory() []VersionData {
return data
}
-15
View File
@@ -53,21 +53,6 @@ func CloseDb() {
Dbpool.Close()
}
func Testf() {
rows, dbErr := Dbpool.Query(Ctx, "select game_name from game")
if dbErr != nil {
logging.GetLogger().Fatal("Query failed", zap.String("error", dbErr.Error()))
}
for rows.Next() {
var gameName string
dbErr = rows.Scan(&gameName)
if dbErr != nil {
logging.GetLogger().Error("Row scan failed", zap.String("error", dbErr.Error()))
}
logging.GetLogger().Debug("Game found", zap.String("name", gameName))
}
}
func ResetGameIdSeq() {
_, err := Dbpool.Query(Ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
if err != nil {
+42
View File
@@ -0,0 +1,42 @@
package server
import (
"net/http"
"github.com/labstack/echo/v5"
"music-server/internal/backend"
)
type CharacterHandler struct {
}
func NewCharacterHandler() *CharacterHandler {
return &CharacterHandler{}
}
// GetCharacterList godoc
// @Summary Get list of characters
// @Description Returns a list of all available characters
// @Tags characters
// @Accept json
// @Produce json
// @Success 200 {array} string
// @Router /characters [get]
func (c *CharacterHandler) GetCharacterList(ctx *echo.Context) error {
characters := backend.GetCharacterList()
return ctx.JSON(http.StatusOK, characters)
}
// GetCharacter godoc
// @Summary Get character image
// @Description Returns the image for a specific character
// @Tags characters
// @Accept json
// @Produce image/png
// @Param name query string true "Character name"
// @Success 200 {file} file
// @Router /character [get]
func (c *CharacterHandler) GetCharacter(ctx *echo.Context) error {
character := ctx.QueryParam("name")
return ctx.File(backend.GetCharacter(character))
}
@@ -5,45 +5,9 @@ import (
"net/http"
"testing"
"music-server/internal/backend"
"music-server/internal/db"
"github.com/stretchr/testify/assert"
)
// TestHealthCheck verifies the health endpoint returns database status
func TestHealthCheck(t *testing.T) {
// Setup database
db.TestSetupDB(t)
defer db.TestTearDownDB(t)
e := StartTestServer(t)
resp := MakeTestRequest(t, e, "GET", "/health")
assert.Equal(t, http.StatusOK, resp.Code)
var healthData map[string]string
err := json.Unmarshal(resp.Body.Bytes(), &healthData)
assert.NoError(t, err)
assert.NotEmpty(t, healthData)
assert.Equal(t, "up", healthData["status"])
}
// TestGetVersion verifies the version endpoint returns version history
func TestGetVersion(t *testing.T) {
e := StartTestServer(t)
resp := MakeTestRequest(t, e, "GET", "/version")
assert.Equal(t, http.StatusOK, resp.Code)
var versionData backend.VersionData
err := json.Unmarshal(resp.Body.Bytes(), &versionData)
assert.NoError(t, err)
assert.NotEmpty(t, versionData.Version)
assert.NotEmpty(t, versionData.Changelog)
assert.NotEmpty(t, versionData.History)
}
// TestGetCharacterList verifies the characters endpoint returns list of characters
func TestGetCharacterList(t *testing.T) {
e := StartTestServer(t)
@@ -81,16 +45,3 @@ func TestGetCharacterNotFound(t *testing.T) {
// Should return 404 or similar error
assert.NotEqual(t, http.StatusOK, resp.Code)
}
// TestDBTest verifies the database test endpoint
func TestDBTest(t *testing.T) {
// Setup database
db.TestSetupDB(t)
defer db.TestTearDownDB(t)
e := StartTestServer(t)
resp := MakeTestRequest(t, e, "GET", "/dbtest")
assert.Equal(t, http.StatusOK, resp.Code)
assert.Contains(t, resp.Body.String(), "TestedDB")
}
+28
View File
@@ -0,0 +1,28 @@
package server
import (
"net/http"
"github.com/labstack/echo/v5"
"music-server/internal/db"
)
type HealthHandler struct {
}
func NewHealthHandler() *HealthHandler {
return &HealthHandler{}
}
// HealthCheck godoc
//
// @Summary Check server health
// @Description Returns the health status of the server
// @Tags health
// @Accept json
// @Produce json
// @Success 200 {string} string "OK"
// @Router /health [get]
func (h *HealthHandler) HealthCheck(ctx *echo.Context) error {
return ctx.JSON(http.StatusOK, db.Health())
}
+29
View File
@@ -0,0 +1,29 @@
package server
import (
"encoding/json"
"net/http"
"testing"
"music-server/internal/db"
"github.com/stretchr/testify/assert"
)
// TestHealthCheck verifies the health endpoint returns database status
func TestHealthCheck(t *testing.T) {
// Setup database
db.TestSetupDB(t)
defer db.TestTearDownDB(t)
e := StartTestServer(t)
resp := MakeTestRequest(t, e, "GET", "/health")
assert.Equal(t, http.StatusOK, resp.Code)
var healthData map[string]string
err := json.Unmarshal(resp.Body.Bytes(), &healthData)
assert.NoError(t, err)
assert.NotEmpty(t, healthData)
assert.Equal(t, "up", healthData["status"])
}
-86
View File
@@ -1,86 +0,0 @@
package server
import (
"music-server/internal/backend"
"music-server/internal/db"
"net/http"
"github.com/labstack/echo/v5"
)
type IndexHandler struct {
}
func NewIndexHandler() *IndexHandler {
return &IndexHandler{}
}
// GetVersion godoc
//
// @Summary Getting the version of the backend
// @Description get string by ID
// @Tags accounts
// @Accept json
// @Produce json
// @Success 200 {object} backend.VersionData
// @Failure 404 {object} string
// @Router /version [get]
func (i *IndexHandler) GetVersion(ctx *echo.Context) error {
versionHistory := backend.GetVersionHistory()
if versionHistory.Version == "" {
return ctx.JSON(http.StatusNotFound, "version not found")
}
return ctx.JSON(http.StatusOK, versionHistory)
}
// GetDBTest godoc
// @Summary Test database connection
// @Description Tests the database connection
// @Tags database
// @Accept json
// @Produce json
// @Success 200 {string} string "TestedDB"
// @Router /dbtest [get]
func (i *IndexHandler) GetDBTest(ctx *echo.Context) error {
backend.TestDB()
return ctx.JSON(http.StatusOK, "TestedDB")
}
// HealthCheck godoc
// @Summary Check server health
// @Description Returns the health status of the server
// @Tags health
// @Accept json
// @Produce json
// @Success 200 {string} string "OK"
// @Router /health [get]
func (i *IndexHandler) HealthCheck(ctx *echo.Context) error {
return ctx.JSON(http.StatusOK, db.Health())
}
// GetCharacterList godoc
// @Summary Get list of characters
// @Description Returns a list of all available characters
// @Tags characters
// @Accept json
// @Produce json
// @Success 200 {array} string
// @Router /characters [get]
func (i *IndexHandler) GetCharacterList(ctx *echo.Context) error {
characters := backend.GetCharacterList()
return ctx.JSON(http.StatusOK, characters)
}
// GetCharacter godoc
// @Summary Get character image
// @Description Returns the image for a specific character
// @Tags characters
// @Accept json
// @Produce image/png
// @Param name query string true "Character name"
// @Success 200 {file} file
// @Router /character [get]
func (i *IndexHandler) GetCharacter(ctx *echo.Context) error {
character := ctx.QueryParam("name")
return ctx.File(backend.GetCharacter(character))
}
+12 -8
View File
@@ -10,8 +10,8 @@ import (
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
echoSwagger "github.com/swaggo/echo-swagger/v2"
"music-server/internal/logging"
"go.uber.org/zap"
"music-server/internal/logging"
)
// @Title MusicServer API
@@ -29,7 +29,7 @@ import (
// @BasePath /
func (s *Server) RegisterRoutes() http.Handler {
e := echo.New()
// Serve OpenAPI spec at /openapi
e.GET("/openapi", echo.WrapHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
@@ -57,12 +57,16 @@ func (s *Server) RegisterRoutes() http.Handler {
// Swagger UI
e.GET("/swagger/*", echoSwagger.WrapHandler)
index := NewIndexHandler()
e.GET("/version", index.GetVersion)
e.GET("/dbtest", index.GetDBTest)
e.GET("/health", index.HealthCheck)
e.GET("/character", index.GetCharacter)
e.GET("/characters", index.GetCharacterList)
health := NewHealthHandler()
e.GET("/health", health.HealthCheck)
version := NewVersionHandler()
e.GET("/version", version.GetLatestVersion)
e.GET("/version/history", version.GetVersionHistory)
character := NewCharacterHandler()
e.GET("/character", character.GetCharacter)
e.GET("/characters", character.GetCharacterList)
download := NewDownloadHandler()
e.GET("/download", download.checkLatest)
+51
View File
@@ -0,0 +1,51 @@
package server
import (
"net/http"
"github.com/labstack/echo/v5"
"music-server/internal/backend"
)
type VersionHandler struct {
}
func NewVersionHandler() *VersionHandler {
return &VersionHandler{}
}
// GetVersionHistory godoc
//
// @Summary Getting the version history of the backend
// @Description get version history
// @Tags version
// @Accept json
// @Produce json
// @Success 200 {array} backend.VersionData
// @Failure 404 {object} string
// @Router /version/history [get]
func (v *VersionHandler) GetVersionHistory(ctx *echo.Context) error {
versionHistory := backend.GetVersionHistory()
if len(versionHistory) == 0 {
return ctx.JSON(http.StatusNotFound, "version not found")
}
return ctx.JSON(http.StatusOK, versionHistory)
}
// GetLatestVersion godoc
//
// @Summary Getting the latest version of the backend
// @Description get latest version info
// @Tags version
// @Accept json
// @Produce json
// @Success 200 {object} backend.VersionData
// @Failure 404 {object} string
// @Router /version [get]
func (v *VersionHandler) GetLatestVersion(ctx *echo.Context) error {
latestVersion := backend.GetLatestVersion()
if latestVersion.Version == "" {
return ctx.JSON(http.StatusNotFound, "version not found")
}
return ctx.JSON(http.StatusOK, latestVersion)
}
+40
View File
@@ -0,0 +1,40 @@
package server
import (
"encoding/json"
"net/http"
"testing"
"music-server/internal/backend"
"github.com/stretchr/testify/assert"
)
// TestGetLatestVersion verifies the version endpoint returns latest version
func TestGetLatestVersion(t *testing.T) {
e := StartTestServer(t)
resp := MakeTestRequest(t, e, "GET", "/version")
assert.Equal(t, http.StatusOK, resp.Code)
var versionData backend.VersionData
err := json.Unmarshal(resp.Body.Bytes(), &versionData)
assert.NoError(t, err)
assert.NotEmpty(t, versionData.Version)
assert.NotEmpty(t, versionData.Changelog)
}
// TestGetVersionHistory verifies the version history endpoint returns version history
func TestGetVersionHistory(t *testing.T) {
e := StartTestServer(t)
resp := MakeTestRequest(t, e, "GET", "/version/history")
assert.Equal(t, http.StatusOK, resp.Code)
var versionHistory []backend.VersionData
err := json.Unmarshal(resp.Body.Bytes(), &versionHistory)
assert.NoError(t, err)
assert.NotEmpty(t, versionHistory)
assert.NotEmpty(t, versionHistory[0].Version)
assert.NotEmpty(t, versionHistory[0].Changelog)
}