Skip to content

Commit

Permalink
refactor(route): centralized routes and handlers in server pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
mgjules committed Apr 11, 2022
1 parent 697e54d commit c557c6a
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 164 deletions.
4 changes: 2 additions & 2 deletions app/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func ProvideSpoty(cfg *config.Config, cache *cache.Cache) (*spoty.Spoty, error)
return spoty, nil
}

func ProvideServer(lc fx.Lifecycle, cfg *config.Config) (*server.Server, error) {
server := server.New(cfg.Prod, cfg.Host, cfg.Port)
func ProvideServer(lc fx.Lifecycle, cfg *config.Config, spoty *spoty.Spoty) (*server.Server, error) {
server := server.New(cfg.Prod, cfg.Host, cfg.Port, spoty)

lc.Append(fx.Hook{
OnStart: func(context.Context) error {
Expand Down
5 changes: 1 addition & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/JulesMike/spoty/app"
"github.com/JulesMike/spoty/server"
"github.com/JulesMike/spoty/spoty"
"go.uber.org/fx"
)

Expand All @@ -27,9 +26,7 @@ func main() {
func run() error {
app := fx.New(
app.DefaultProviders,
fx.Invoke(func(spoty *spoty.Spoty, server *server.Server) {
spoty.RegisterRoutes(server.APIRoute())
}),
fx.Invoke(func(server *server.Server) {}),
)
if err := app.Err(); err != nil {
return err
Expand Down
29 changes: 29 additions & 0 deletions server/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package server

import (
"net/http"

"github.com/gin-gonic/gin"
)

func (s *Server) unauthenticatedOnly() gin.HandlerFunc {
return func(c *gin.Context) {
if s.spoty.Client != nil {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "you are already authenticated"})
return
}

c.Next()
}
}

func (s *Server) authenticatedOnly() gin.HandlerFunc {
return func(c *gin.Context) {
if s.spoty.Client == nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "you must be authenticated to access this endpoint"})
return
}

c.Next()
}
}
101 changes: 95 additions & 6 deletions server/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,112 @@ type Error struct {
Error string `json:"error"`
}

// registerHealthCheck godoc
// handleHealthCheck godoc
// @Summary Health Check
// @Description checks if server is running
// @Tags core
// @Produce json
// @Success 200 {object} Success
// @Router /api [get]
func (s *Server) registerHealthCheck() {
s.APIRoute().GET("/", func(c *gin.Context) {
func (s *Server) handleHealthCheck() gin.HandlerFunc {
return func(c *gin.Context) {
c.JSON(http.StatusOK, Success{Success: "i'm alright!"})
})
}
}

func (s *Server) registerSwagger() {
func (s *Server) handleSwagger() gin.HandlerFunc {
docs.SwaggerInfo.Host = s.addr
docs.SwaggerInfo.BasePath = "/"

url := ginSwagger.URL("http://" + s.addr + "/swagger/doc.json")
s.router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
return ginSwagger.WrapHandler(swaggerFiles.Handler, url)
}

// handleCurrentTrack godoc
// @Summary Current Playing Track
// @Description returns information about the current playing track
// @Tags spoty
// @Produce json
// @Success 200 {object} spotify.FullTrack "returns full track information"
// @Failure 401 {object} server.Error "not authenticated"
// @Failure 404 {object} server.Error "no current playing track found"
// @Router /api/current [get]
func (s *Server) handleCurrentTrack(c *gin.Context) {
track, err := s.spoty.TrackCurrentlyPlaying()
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "could not retrieve currently playing track"})
return
}

c.JSON(http.StatusOK, track)
}

// handleCurrentTrackImages godoc
// @Summary Album Images of Current Playing Track
// @Description returns the album images of the current playing track
// @Tags spoty
// @Produce json
// @Success 200 {array} spoty.Image "returns album images"
// @Failure 401 {object} server.Error "not authenticated"
// @Failure 404 {object} server.Error "no current playing track found"
// @Failure 500 {object} server.Error "album images could not be processed"
// @Router /api/current/images [get]
func (s *Server) handleCurrentTrackImages(c *gin.Context) {
track, err := s.spoty.TrackCurrentlyPlaying()
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "could not retrieve currently playing track"})
return
}

images, err := s.spoty.TrackImages(track)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "could not process images for currently playing track"})
return
}

c.JSON(http.StatusOK, images)
}

// handleAuthenticate godoc
// @Summary Authentication
// @Description redirects user to spotify for authentication
// @Tags spoty
// @Produce json
// @Success 302 {string} string "redirection to spotify"
// @Failure 403 {object} server.Error "already authenticated"
// @Router /api/authenticate [get]
func (s *Server) handleAuthenticate(c *gin.Context) {
c.Redirect(http.StatusFound, s.spoty.Auth.AuthURL(s.spoty.State))
}

// handleCallback godoc
// @Summary Callback
// @Description spotify redirects to the this endpoint on success
// @Tags spoty
// @Produce json
// @Param code query string true "code from spotify"
// @Param state query string true "state from spotify"
// @Success 200 {object} server.Success "authenticated successfully"
// @Failure 403 {object} server.Error "already authenticated"
// @Failure 403 {object} server.Error "could not retrieve token"
// @Failure 404 {object} server.Error "could not retrieve current user"
// @Router /api/callback [get]
func (s *Server) handleCallback(c *gin.Context) {
tok, err := s.spoty.Auth.Token(s.spoty.State, c.Request)
if err != nil {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "could not retrieve token"})
return
}

client := s.spoty.Auth.NewClient(tok)
if _, err := client.CurrentUser(); err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "could not retrieve current user"})
return
}

client.AutoRetry = true

s.spoty.Client = &client

c.JSON(http.StatusOK, gin.H{"success": "welcome, you are now authenticated!"})
}
35 changes: 30 additions & 5 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,26 @@ import (
"net/http"
"time"

"github.com/JulesMike/spoty/spoty"
"github.com/gin-gonic/gin"
)

type Server struct {
router *gin.Engine
http *http.Server
spoty *spoty.Spoty
addr string
}

func New(prod bool, host string, port int) *Server {
func New(prod bool, host string, port int, spoty *spoty.Spoty) *Server {
if prod {
gin.SetMode(gin.ReleaseMode)
}

s := Server{
router: gin.Default(),
addr: fmt.Sprintf("%s:%d", host, port),
spoty: spoty,
}

s.http = &http.Server{
Expand All @@ -35,14 +38,36 @@ func New(prod bool, host string, port int) *Server {
ReadHeaderTimeout: 2 * time.Second,
}

s.registerHealthCheck()
s.registerSwagger()
s.registerRoutes()

return &s
}

func (s *Server) APIRoute() *gin.RouterGroup {
return s.router.Group("/api")
func (s *Server) registerRoutes() {
// Swagger
s.router.GET("/swagger/*any", s.handleSwagger())

api := s.router.Group("/api")
{
// Health Check
api.GET("/", s.handleHealthCheck())

// Guest routes
guest := api.Group("/")
guest.Use(s.unauthenticatedOnly())
{
guest.GET("/authenticate", s.handleAuthenticate)
guest.GET("/callback", s.handleCallback)
}

// Authenticated routes
authenticated := api.Group("/")
authenticated.Use(s.authenticatedOnly())
{
authenticated.GET("/current", s.handleCurrentTrack)
authenticated.GET("/current/images", s.handleCurrentTrackImages)
}
}
}

func (s *Server) Start() error {
Expand Down
136 changes: 0 additions & 136 deletions spoty/route.go

This file was deleted.

Loading

0 comments on commit c557c6a

Please sign in to comment.