gomusic/routes.go

184 lines
5.2 KiB
Go

package main
import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm/clause"
"log/slog"
"net/http"
"strconv"
)
// Pagination helper middleware
// This just checks and inserts some default pagination values into the request context
func paginator() gin.HandlerFunc {
return func(c *gin.Context) {
queryPage := c.DefaultQuery("page", "1")
queryPageSize := c.DefaultQuery("page_size", "10")
// add some defaults here if user gives us empty values
if queryPage == "" {
queryPage = "1"
}
if queryPageSize == "" {
queryPageSize = "10"
}
page, err := strconv.Atoi(queryPage)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"Error": "Failed to convert page parameter to number"})
c.Abort()
return
}
pageSize, err := strconv.Atoi(queryPageSize)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"Error": "Failed to convert page_size parameter to number"})
c.Abort()
return
}
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 1
}
// Calculate the correct SQL offset for this page
offset := (page - 1) * pageSize
c.Set("page", page)
c.Set("page_size", pageSize)
c.Set("page_offset", offset)
// Process request
c.Next()
}
}
func (env *Env) alive(c *gin.Context) {
c.String(http.StatusOK, "yes!")
}
func (env *Env) getArtistByID(c *gin.Context) {
artistID := c.Params.ByName("artistID")
spotifyAuthToken := c.GetString("spotifyAuthToken")
if artistID == "" || spotifyAuthToken == "Bearer " {
c.JSON(http.StatusBadRequest, gin.H{"Error": "Could not find required parameters and/or required authentication tokens"})
return
}
spotifyResponse, err := getSpotifyArtistData(artistID, spotifyAuthToken)
if err != nil {
// cast the error back into our custom type
// so we can get context on why things failed
statusCode := err.(*ResponseError).StatusCode
switch statusCode {
case 404:
c.JSON(http.StatusNotFound, gin.H{"Error": "This artist does not exist"})
return
case 400:
c.JSON(http.StatusBadRequest, gin.H{"Error": "Bad request"})
return
}
slog.Error("[GOMUSIC] Failed to request latest spotify data from API", "Error", err)
c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request the latest data from spotify API"})
return
}
// Update DB here
var genreList []Genre
for _, val := range spotifyResponse.Genres {
genreList = append(genreList, Genre{Name: val})
}
artistProfile := &ArtistProfile{
SpotifyID: spotifyResponse.ID,
Name: spotifyResponse.Name,
Popularity: spotifyResponse.Popularity,
Genres: genreList,
}
dbResult := env.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "spotify_id"}},
UpdateAll: true,
}).Create(&artistProfile)
if dbResult.Error != nil {
slog.Error("[GOMUSIC] Failed to store response in local database", "Error", err)
}
c.JSON(http.StatusOK, artistProfile)
}
func (env *Env) getArtistByName(c *gin.Context) {
artistName, exists := c.GetQuery("name")
if !exists {
c.JSON(http.StatusBadRequest, gin.H{"Error": "name parameter was not supplied"})
return
}
pageSize := c.GetInt("page_size")
pageOffset := c.GetInt("page_offset")
// Lookup this name in the DB and return any ArtistProfile objects
// NOTE: This is case insensitive for ascii on sqlite
// NOTE: However unicode is treated case sensitive due to very difficult conversions for some languages/characters
// NOTE: In future better DBs with unicode support such as postgres should be used
var artistProfiles []ArtistProfile
searchString := fmt.Sprintf("%%%s%%", artistName)
dbResult := env.db.Where("name LIKE ?", searchString).Preload("Genres").Offset(pageOffset).Limit(pageSize).Find(&artistProfiles)
if dbResult.Error != nil {
slog.Error("[GOMUSIC] Failed to query local database for artist name", "Name", artistName)
c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to lookup name"})
return
}
c.JSON(http.StatusOK, artistProfiles)
}
func (env *Env) getGenres(c *gin.Context) {
var genres []Genre
pageSize := c.GetInt("page_size")
pageOffset := c.GetInt("page_offset")
dbResult := env.db.Offset(pageOffset).Limit(pageSize).Find(&genres)
if dbResult.Error != nil {
slog.Error("[GOMUSIC] Failed to query local database for genre data")
c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to query database for genre data"})
return
}
recordNum := dbResult.RowsAffected
// Construct simple slice for response
genreList := make([]string, 0, recordNum)
if recordNum > 0 {
for _, g := range genres {
genreList = append(genreList, g.Name)
}
}
c.JSON(http.StatusOK, genreList)
}
func (env *Env) createGenre(c *gin.Context) {
var userGenre Genre
if c.ShouldBind(&userGenre) == nil {
// If we didn't get the correct data abort early
if userGenre.Name == "" {
c.JSON(http.StatusBadRequest, gin.H{"Error": "Could not find genre name"})
return
}
dbResult := env.db.Clauses(clause.OnConflict{
DoNothing: true,
}).Create(&userGenre)
if dbResult.Error != nil {
slog.Error("[GOMUSIC] Failed to create new genre record in DB")
c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to create new genre record"})
return
}
c.String(http.StatusOK, "Success")
return
}
c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to parse input data"})
}