- Add nginx as single entry point: /api/* → backend, /* → web - NEXT_PUBLIC_API_URL="" so all API calls are relative (go through nginx) - Add Spotify playlist import (Client Credentials OAuth, up to 500 tracks) - Add Yandex/Spotify tabbed import UI on /playlists - Add stream overlay system (SSE + polling fallback, 9 styles) - Reorganize pages into (main) route group - Add QueuePanel, VersionsPanel, Toaster components - Add overlay settings tab in /settings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
package router
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-contrib/cors"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/toyffee/party-mix/internal/config"
|
|
"github.com/toyffee/party-mix/internal/handlers"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func New(db *gorm.DB, cfg *config.Config) *gin.Engine {
|
|
r := gin.Default()
|
|
|
|
origins := strings.Split(cfg.AllowedOrigins, ",")
|
|
for i, o := range origins {
|
|
origins[i] = strings.TrimSpace(o)
|
|
}
|
|
|
|
r.Use(cors.New(cors.Config{
|
|
AllowOrigins: origins,
|
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
|
ExposeHeaders: []string{"Content-Length", "Content-Range", "Accept-Ranges"},
|
|
AllowCredentials: true,
|
|
}))
|
|
|
|
r.GET("/health", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
|
})
|
|
|
|
proxy := r.Group("/api/proxy")
|
|
{
|
|
proxy.GET("/search", handlers.SearchHandler)
|
|
proxy.GET("/top", handlers.TopChartsHandler)
|
|
proxy.GET("/img", handlers.ImgProxyHandler)
|
|
proxy.GET("/mp3", handlers.MP3ProxyHandler)
|
|
proxy.GET("/yandex-playlist", handlers.YandexPlaylistHandler)
|
|
proxy.GET("/spotify-playlist", handlers.SpotifyPlaylistHandler)
|
|
}
|
|
|
|
auth := r.Group("/api/auth")
|
|
{
|
|
auth.POST("/register", handlers.Register(db))
|
|
auth.POST("/login", handlers.Login(db, cfg.JWTSecret, cfg.CookieSecure))
|
|
auth.POST("/logout", handlers.Logout(cfg.CookieSecure))
|
|
auth.GET("/me", handlers.AuthRequired(cfg.JWTSecret), handlers.Me(db))
|
|
}
|
|
|
|
r.GET("/api/playlists/public", handlers.GetPublicPlaylists(db))
|
|
|
|
versions := r.Group("/api/versions", handlers.AuthRequired(cfg.JWTSecret))
|
|
{
|
|
versions.GET("", handlers.GetVersions(db))
|
|
versions.POST("", handlers.SaveVersion(db))
|
|
versions.DELETE("", handlers.DeleteVersion(db))
|
|
}
|
|
|
|
playlists := r.Group("/api/playlists", handlers.AuthRequired(cfg.JWTSecret))
|
|
{
|
|
playlists.GET("", handlers.GetPlaylists(db))
|
|
playlists.POST("", handlers.CreatePlaylist(db))
|
|
playlists.GET("/:id", handlers.GetPlaylist(db))
|
|
playlists.PUT("/:id", handlers.UpdatePlaylist(db))
|
|
playlists.DELETE("/:id", handlers.DeletePlaylist(db))
|
|
playlists.POST("/:id/tracks", handlers.AddTrackToPlaylist(db))
|
|
}
|
|
|
|
overlay := r.Group("/api/overlay")
|
|
{
|
|
overlay.PUT("/state", handlers.AuthRequired(cfg.JWTSecret), handlers.PushOverlayState)
|
|
overlay.GET("/:token/state", handlers.GetOverlayState)
|
|
overlay.GET("/:token/stream", handlers.StreamOverlayState)
|
|
}
|
|
|
|
remote := r.Group("/api/remote")
|
|
{
|
|
remote.POST("", handlers.CreateRemoteRoom)
|
|
remote.PUT("/:id/state", handlers.PushRemoteState)
|
|
remote.GET("/:id/state", handlers.GetRemoteState)
|
|
remote.POST("/:id/command", handlers.SendRemoteCommand)
|
|
remote.GET("/:id/commands", handlers.PollRemoteCommands)
|
|
}
|
|
|
|
parties := r.Group("/api/parties")
|
|
{
|
|
parties.POST("", handlers.CreateParty(db))
|
|
parties.GET("/:id", handlers.GetParty(db))
|
|
parties.GET("/code/:code", handlers.GetPartyByCode(db))
|
|
parties.POST("/:id/participants", handlers.AddParticipant(db))
|
|
parties.DELETE("/:id/participants/:pid", handlers.RemoveParticipant(db))
|
|
parties.POST("/:id/history", handlers.AddHistory(db))
|
|
parties.GET("/:id/history", handlers.GetHistory(db))
|
|
parties.DELETE("/:id/history", handlers.ClearHistory(db))
|
|
}
|
|
|
|
return r
|
|
}
|