backend functioning. Player models init

This commit is contained in:
Ravi Prasad
2025-04-30 21:19:31 -05:00
parent bde752041f
commit e269bc5145
10 changed files with 278 additions and 0 deletions
+6
View File
@@ -23,3 +23,9 @@ go.work.sum
# env file
.env
data/nba.db
/data/
/data
/data/*
+19
View File
@@ -0,0 +1,19 @@
package config
import (
"log"
"gorm.io/driver/sqlite" // Or Postgres
"gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/models"
)
func InitDB() *gorm.DB {
db, err := gorm.Open(sqlite.Open("/Users/ravi/code/projects/NBA_Go/data/nba.db"), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect database")
}
db.AutoMigrate(&models.PlayerStat{})
return db
}
+35
View File
@@ -0,0 +1,35 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/services"
"github.com/nprasad2077/NBA_Go/models"
)
func FetchPlayerStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
season := c.QueryInt("season", 2025)
// Call service and assign err here
err := services.FetchAndStorePlayerStats(db, season)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"message": "Player stats fetched and saved."})
}
}
func GetAllPlayerStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
var stats []models.PlayerStat
if err := db.Find(&stats).Error; err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(stats)
}
}
+24
View File
@@ -0,0 +1,24 @@
module github.com/nprasad2077/NBA_Go
go 1.24.2
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/gofiber/fiber/v2 v2.52.6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.20.0 // indirect
gorm.io/driver/sqlite v1.5.7 // indirect
gorm.io/gorm v1.26.0 // indirect
)
+41
View File
@@ -0,0 +1,41 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs=
gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
+26
View File
@@ -0,0 +1,26 @@
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/config"
"github.com/nprasad2077/NBA_Go/routes"
"github.com/nprasad2077/NBA_Go/services"
)
func main() {
app := fiber.New()
db := config.InitDB()
// Automatically fetch player stats on startup
go func() {
if err := services.FetchAndStorePlayerStats(db, 2025); err != nil {
log.Println("Initial fetch failed:", err)
} else {
log.Println("Initial player stats fetch successful.")
}
}()
routes.RegisterPlayerRoutes(app, db)
app.Listen(":3001")
}
+36
View File
@@ -0,0 +1,36 @@
package models
import "gorm.io/gorm"
type PlayerStat struct {
gorm.Model
ExternalID int `json:"id"`
PlayerID string `gorm:"not null;uniqueIndex:idx_player_season_team" json:"playerId"`
PlayerName string `json:"playerName"`
Position string `json:"position"`
Age int `json:"age"`
Games int `json:"games"`
MinutesPlayed int `json:"minutesPlayed"`
PER float64 `json:"per"`
TSPercent float64 `json:"tsPercent"`
ThreePAR float64 `json:"threePAR"`
FTR float64 `json:"ftr"`
OffensiveRBPercent float64 `json:"offensiveRBPercent"`
DefensiveRBPercent float64 `json:"defensiveRBPercent"`
TotalRBPercent float64 `json:"totalRBPercent"`
AssistPercent float64 `json:"assistPercent"`
StealPercent float64 `json:"stealPercent"`
BlockPercent float64 `json:"blockPercent"`
TurnoverPercent float64 `json:"turnoverPercent"`
UsagePercent float64 `json:"usagePercent"`
OffensiveWS float64 `json:"offensiveWS"`
DefensiveWS float64 `json:"defensiveWS"`
WinShares float64 `json:"winShares"`
WinSharesPer float64 `json:"winSharesPer"`
OffensiveBox float64 `json:"offensiveBox"`
DefensiveBox float64 `json:"defensiveBox"`
Box float64 `json:"box"`
VORP float64 `json:"vorp"`
Team string `gorm:"not null;uniqueIndex:idx_player_season_team" json:"team"`
Season int `gorm:"not null;uniqueIndex:idx_player_season_team" json:"season"`
}
+14
View File
@@ -0,0 +1,14 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/controllers"
)
func RegisterPlayerRoutes(app *fiber.App, db *gorm.DB) {
api := app.Group("/api/playerstats")
api.Get("/fetch", controllers.FetchPlayerStats(db))
api.Get("/", controllers.GetAllPlayerStats(db)) // ✅ Add this line
}
+48
View File
@@ -0,0 +1,48 @@
package services
import (
"encoding/json"
"fmt"
"log"
"github.com/nprasad2077/NBA_Go/models"
"github.com/nprasad2077/NBA_Go/utils"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func FetchAndStorePlayerStats(db *gorm.DB, season int) error {
url := fmt.Sprintf("http://rest.nbaapi.com/api/PlayerDataAdvanced/query?season=%d&sortBy=Points&ascending=false&pageNumber=1&pageSize=20", season)
body, err := utils.GetJSON(url)
if err != nil {
return err
}
var stats []models.PlayerStat
if err := json.Unmarshal(body, &stats); err != nil {
return err
}
for _, stat := range stats {
// Ensure the season is included from the query param
stat.Season = season
err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "player_id"}, {Name: "season"}, {Name: "team"}},
DoUpdates: clause.AssignmentColumns([]string{
"external_id", "player_name", "position", "age", "games",
"minutes_played", "per", "ts_percent", "three_par", "ftr",
"offensive_rb_percent", "defensive_rb_percent", "total_rb_percent",
"assist_percent", "steal_percent", "block_percent", "turnover_percent",
"usage_percent", "offensive_ws", "defensive_ws", "win_shares",
"win_shares_per", "offensive_box", "defensive_box", "box", "vorp",
}),
}).Create(&stat).Error
if err != nil {
log.Printf("Failed to upsert stat for playerId %s (%s): %v", stat.PlayerID, stat.Team, err)
}
}
return nil
}
+29
View File
@@ -0,0 +1,29 @@
package utils
import (
"errors"
"io/ioutil"
"net/http"
"time"
)
func GetJSON(url string) ([]byte, error) {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New("received non-200 response: " + resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}