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"}) }