288 lines
11 KiB
Go
288 lines
11 KiB
Go
package server
|
|
|
|
import (
|
|
"music-server/cmd/web"
|
|
"music-server/internal/logging"
|
|
"music-server/internal/server/middleware"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/a-h/templ"
|
|
"github.com/labstack/echo/v5"
|
|
echoMiddleware "github.com/labstack/echo/v5/middleware"
|
|
echoSwagger "github.com/swaggo/echo-swagger/v2"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// @Title MusicServer API
|
|
// @version 1.0
|
|
// @description API for the MusicServer application
|
|
// @termsOfService http://sanplex.xyz/terms/
|
|
|
|
// @contact.name Sebastian Olsson
|
|
// @contact.email zarnor91@gmail.com
|
|
|
|
// @license.name MIT
|
|
// @license.url http://opensource.org/licenses/MIT
|
|
|
|
// @host localhost:8080
|
|
// @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")
|
|
http.ServeFile(w, r, "cmd/docs/swagger.json")
|
|
})))
|
|
e.Use(logging.RequestLogger())
|
|
e.Use(echoMiddleware.Recover())
|
|
|
|
e.Use(echoMiddleware.CORSWithConfig(echoMiddleware.CORSConfig{
|
|
AllowOrigins: []string{"https://*", "http://*"},
|
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
|
|
AllowHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
|
AllowCredentials: true,
|
|
MaxAge: 300,
|
|
}))
|
|
|
|
fileServer := http.FileServer(http.FS(web.Assets))
|
|
e.GET("/assets/*", echo.WrapHandler(fileServer))
|
|
|
|
e.GET("/search", echo.WrapHandler(templ.Handler(web.SearchForm())))
|
|
e.POST("/find", echo.WrapHandler(http.HandlerFunc(web.FindSoundtrackWebHandler)))
|
|
|
|
e.Static("/", "/frontend")
|
|
|
|
// Swagger UI
|
|
e.GET("/swagger/*", echoSwagger.WrapHandler)
|
|
|
|
// ============================================
|
|
// Legacy Endpoints (Deprecated - use /api/v1/ instead)
|
|
// ============================================
|
|
deprecatedMiddleware := middleware.DeprecationMiddleware
|
|
|
|
health := NewHealthHandler(s.db)
|
|
e.GET("/health", deprecatedMiddleware(health.HealthCheck))
|
|
|
|
version := NewVersionHandler()
|
|
e.GET("/version", deprecatedMiddleware(version.GetLatestVersion))
|
|
e.GET("/version/history", deprecatedMiddleware(version.GetVersionHistory))
|
|
|
|
character := NewCharacterHandler()
|
|
e.GET("/character", deprecatedMiddleware(character.GetCharacter))
|
|
e.GET("/characters", deprecatedMiddleware(character.GetCharacterList))
|
|
|
|
download := NewDownloadHandler()
|
|
e.GET("/download", deprecatedMiddleware(download.checkLatest))
|
|
e.GET("/download/list", deprecatedMiddleware(download.listAssetsOfLatest))
|
|
e.GET("/download/windows", deprecatedMiddleware(download.downloadLatestWindows))
|
|
e.GET("/download/linux", deprecatedMiddleware(download.downloadLatestLinux))
|
|
|
|
sync := NewSyncHandler()
|
|
syncGroup := e.Group("/sync")
|
|
syncGroup.GET("", deprecatedMiddleware(middleware.SyncCheckMiddleware(sync.SyncSoundtracksNewOnlyChanges)))
|
|
syncGroup.GET("/progress", deprecatedMiddleware(sync.SyncProgress))
|
|
syncGroup.GET("/new", deprecatedMiddleware(middleware.SyncCheckMiddleware(sync.SyncSoundtracksNewOnlyChanges)))
|
|
syncGroup.GET("/full", deprecatedMiddleware(middleware.SyncCheckMiddleware(sync.SyncSoundtracksNewFull)))
|
|
syncGroup.GET("/new/full", deprecatedMiddleware(middleware.SyncCheckMiddleware(sync.SyncSoundtracksNewFull)))
|
|
syncGroup.GET("/quick", deprecatedMiddleware(middleware.SyncCheckMiddleware(sync.SyncSoundtracksNewOnlyChanges)))
|
|
syncGroup.GET("/reset", deprecatedMiddleware(middleware.SyncCheckMiddleware(sync.ResetDB)))
|
|
|
|
music := NewMusicHandler()
|
|
musicGroup := e.Group("/music")
|
|
musicGroup.GET("", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetSong)))
|
|
musicGroup.GET("/soundTest", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetSoundCheckSong)))
|
|
musicGroup.GET("/reset", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.ResetMusic)))
|
|
musicGroup.GET("/rand", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetRandomSong)))
|
|
musicGroup.GET("/rand/low", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetRandomSongLowChance)))
|
|
musicGroup.GET("/rand/classic", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetRandomSongClassic)))
|
|
musicGroup.GET("/info", deprecatedMiddleware(music.GetSongInfo))
|
|
musicGroup.GET("/list", deprecatedMiddleware(music.GetPlayedSongs))
|
|
musicGroup.GET("/next", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetNextSong)))
|
|
musicGroup.GET("/previous", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetPreviousSong)))
|
|
musicGroup.GET("/all", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetAllSoundtracksRandom)))
|
|
musicGroup.GET("/all/order", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetAllSoundtracks)))
|
|
musicGroup.GET("/all/random", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.GetAllSoundtracksRandom)))
|
|
musicGroup.PUT("/played", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.PutPlayed)))
|
|
musicGroup.GET("/addQue", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.AddLatestToQue)))
|
|
musicGroup.GET("/addPlayed", deprecatedMiddleware(middleware.SyncCheckMiddleware(music.AddLatestPlayed)))
|
|
|
|
// ============================================
|
|
// API v1 Routes with Token Authentication
|
|
// ============================================
|
|
|
|
// Create /api/v1 group
|
|
apiV1 := e.Group("/api/v1")
|
|
|
|
// Create rate limiter for token creation: 10 requests per minute per IP
|
|
tokenRateLimiter := middleware.NewRateLimiter(10, time.Minute)
|
|
tokenRateLimitMiddleware := middleware.RateLimitMiddleware(tokenRateLimiter)
|
|
|
|
// Token endpoints (public - no token required)
|
|
tokenGroup := apiV1.Group("/token")
|
|
tokenGroup.POST("", func(c *echo.Context) error {
|
|
return s.tokenHandler.CreateTokenHandler(c)
|
|
}, tokenRateLimitMiddleware)
|
|
tokenGroup.DELETE("", func(c *echo.Context) error {
|
|
return s.tokenHandler.DeleteTokenHandler(c)
|
|
})
|
|
tokenGroup.POST("/cleanup", func(c *echo.Context) error {
|
|
return s.tokenHandler.CleanupExpiredSessionsHandler(c)
|
|
})
|
|
|
|
// Protected endpoints - require valid token
|
|
// Create token auth middleware with pool access
|
|
tokenAuthMiddleware := middleware.TokenAuthMiddleware(s.db.Pool)
|
|
protectedV1 := apiV1.Group("", tokenAuthMiddleware)
|
|
|
|
// Statistics endpoints (protected)
|
|
statisticsHandler := s.statisticsHandler
|
|
statsGroup := protectedV1.Group("/statistics")
|
|
statsGroup.GET("/soundtracks/most-played", func(c *echo.Context) error {
|
|
return statisticsHandler.GetMostPlayedSoundtracks(c)
|
|
})
|
|
statsGroup.GET("/soundtracks/least-played", func(c *echo.Context) error {
|
|
return statisticsHandler.GetLeastPlayedSoundtracks(c)
|
|
})
|
|
statsGroup.GET("/soundtracks/never-played", func(c *echo.Context) error {
|
|
return statisticsHandler.GetNeverPlayedSoundtracks(c)
|
|
})
|
|
statsGroup.GET("/soundtracks/last-played", func(c *echo.Context) error {
|
|
return statisticsHandler.GetLastPlayedSoundtracks(c)
|
|
})
|
|
statsGroup.GET("/soundtracks/oldest-played", func(c *echo.Context) error {
|
|
return statisticsHandler.GetOldestPlayedSoundtracks(c)
|
|
})
|
|
statsGroup.GET("/songs/most-played", func(c *echo.Context) error {
|
|
return statisticsHandler.GetMostPlayedSongs(c)
|
|
})
|
|
statsGroup.GET("/songs/least-played", func(c *echo.Context) error {
|
|
return statisticsHandler.GetLeastPlayedSongs(c)
|
|
})
|
|
statsGroup.GET("/summary", func(c *echo.Context) error {
|
|
return statisticsHandler.GetStatisticsSummary(c)
|
|
})
|
|
|
|
// Version endpoints (protected)
|
|
versionGroup := protectedV1.Group("/version")
|
|
versionGroup.GET("", func(c *echo.Context) error {
|
|
return version.GetLatestVersion(c)
|
|
})
|
|
versionGroup.GET("/history", func(c *echo.Context) error {
|
|
return version.GetVersionHistory(c)
|
|
})
|
|
|
|
// Character endpoints (protected)
|
|
characterGroup := protectedV1.Group("/character")
|
|
characterGroup.GET("", func(c *echo.Context) error {
|
|
return character.GetCharacter(c)
|
|
})
|
|
characterGroup.GET("/list", func(c *echo.Context) error {
|
|
return character.GetCharacterList(c)
|
|
})
|
|
|
|
// Download endpoints (protected)
|
|
downloadGroup := protectedV1.Group("/download")
|
|
downloadGroup.GET("", func(c *echo.Context) error {
|
|
return download.checkLatest(c)
|
|
})
|
|
downloadGroup.GET("/list", func(c *echo.Context) error {
|
|
return download.listAssetsOfLatest(c)
|
|
})
|
|
downloadGroup.GET("/windows", func(c *echo.Context) error {
|
|
return download.downloadLatestWindows(c)
|
|
})
|
|
downloadGroup.GET("/linux", func(c *echo.Context) error {
|
|
return download.downloadLatestLinux(c)
|
|
})
|
|
|
|
// Sync endpoints (protected)
|
|
protectedSyncGroup := protectedV1.Group("/sync")
|
|
protectedSyncGroup.GET("", func(c *echo.Context) error {
|
|
return sync.SyncSoundtracksNewOnlyChanges(c)
|
|
})
|
|
protectedSyncGroup.GET("/progress", func(c *echo.Context) error {
|
|
return sync.SyncProgress(c)
|
|
})
|
|
protectedSyncGroup.GET("/new", func(c *echo.Context) error {
|
|
return sync.SyncSoundtracksNewOnlyChanges(c)
|
|
})
|
|
protectedSyncGroup.GET("/full", func(c *echo.Context) error {
|
|
return sync.SyncSoundtracksNewFull(c)
|
|
})
|
|
protectedSyncGroup.GET("/new/full", func(c *echo.Context) error {
|
|
return sync.SyncSoundtracksNewFull(c)
|
|
})
|
|
protectedSyncGroup.GET("/quick", func(c *echo.Context) error {
|
|
return sync.SyncSoundtracksNewOnlyChanges(c)
|
|
})
|
|
protectedSyncGroup.GET("/reset", func(c *echo.Context) error {
|
|
return sync.ResetDB(c)
|
|
})
|
|
|
|
// Music endpoints (protected)
|
|
protectedMusicGroup := protectedV1.Group("/music")
|
|
protectedMusicGroup.GET("", func(c *echo.Context) error {
|
|
return music.GetSong(c)
|
|
})
|
|
protectedMusicGroup.GET("/soundTest", func(c *echo.Context) error {
|
|
return music.GetSoundCheckSong(c)
|
|
})
|
|
protectedMusicGroup.GET("/reset", func(c *echo.Context) error {
|
|
return music.ResetMusic(c)
|
|
})
|
|
protectedMusicGroup.GET("/rand", func(c *echo.Context) error {
|
|
return music.GetRandomSong(c)
|
|
})
|
|
protectedMusicGroup.GET("/rand/low", func(c *echo.Context) error {
|
|
return music.GetRandomSongLowChance(c)
|
|
})
|
|
protectedMusicGroup.GET("/rand/classic", func(c *echo.Context) error {
|
|
return music.GetRandomSongClassic(c)
|
|
})
|
|
protectedMusicGroup.GET("/info", func(c *echo.Context) error {
|
|
return music.GetSongInfo(c)
|
|
})
|
|
protectedMusicGroup.GET("/list", func(c *echo.Context) error {
|
|
return music.GetPlayedSongs(c)
|
|
})
|
|
protectedMusicGroup.GET("/next", func(c *echo.Context) error {
|
|
return music.GetNextSong(c)
|
|
})
|
|
protectedMusicGroup.GET("/previous", func(c *echo.Context) error {
|
|
return music.GetPreviousSong(c)
|
|
})
|
|
protectedMusicGroup.GET("/all", func(c *echo.Context) error {
|
|
return music.GetAllSoundtracksRandom(c)
|
|
})
|
|
protectedMusicGroup.GET("/all/order", func(c *echo.Context) error {
|
|
return music.GetAllSoundtracks(c)
|
|
})
|
|
protectedMusicGroup.GET("/all/random", func(c *echo.Context) error {
|
|
return music.GetAllSoundtracksRandom(c)
|
|
})
|
|
protectedMusicGroup.PUT("/played", func(c *echo.Context) error {
|
|
return music.PutPlayed(c)
|
|
})
|
|
protectedMusicGroup.GET("/addQue", func(c *echo.Context) error {
|
|
return music.AddLatestToQue(c)
|
|
})
|
|
protectedMusicGroup.GET("/addPlayed", func(c *echo.Context) error {
|
|
return music.AddLatestPlayed(c)
|
|
})
|
|
|
|
routes := e.Router().Routes()
|
|
sort.Slice(routes, func(i, j int) bool {
|
|
return routes[i].Path < routes[j].Path
|
|
})
|
|
for _, r := range routes {
|
|
if (r.Method == "GET" || r.Method == "POST" || r.Method == "PUT" || r.Method == "DELETE") && !strings.Contains(r.Name, "github") {
|
|
logging.GetLogger().Debug("Registered route", zap.String("method", r.Method), zap.String("path", r.Path))
|
|
}
|
|
}
|
|
return e
|
|
}
|