populate both tables in loop

This commit is contained in:
Ravi Prasad
2025-04-30 22:49:38 -05:00
parent 80e4823b35
commit e3c8c76f35
15 changed files with 506 additions and 32 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ func InitDB() *gorm.DB {
log.Fatal("Failed to connect database") log.Fatal("Failed to connect database")
} }
db.AutoMigrate(&models.PlayerStat{}) db.AutoMigrate(&models.PlayerAdvancedStat{})
db.AutoMigrate(&models.PlayerTotalStat{})
return db return db
} }
@@ -7,12 +7,12 @@ import (
"github.com/nprasad2077/NBA_Go/models" "github.com/nprasad2077/NBA_Go/models"
) )
func FetchPlayerStats(db *gorm.DB) fiber.Handler { func FetchPlayerAdvancedStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
season := c.QueryInt("season", 2025) season := c.QueryInt("season", 2025)
// Call service and assign err here // Call service and assign err here
err := services.FetchAndStorePlayerStats(db, season) err := services.FetchAndStorePlayerAdvancedStats(db, season)
if err != nil { if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()}) return c.Status(500).JSON(fiber.Map{"error": err.Error()})
} }
@@ -37,10 +37,10 @@ func FetchPlayerStats(db *gorm.DB) fiber.Handler {
// @Param sortBy query string false "Field to sort by (e.g., per, games, winShares)" // @Param sortBy query string false "Field to sort by (e.g., per, games, winShares)"
// @Param ascending query bool false "Sort ascending (default false)" // @Param ascending query bool false "Sort ascending (default false)"
// @Success 200 {object} map[string]interface{} // @Success 200 {object} map[string]interface{}
// @Router /api/playerstats [get] // @Router /api/playeradvancedstats [get]
func GetAllPlayerStats(db *gorm.DB) fiber.Handler { func GetAllAdvancedPlayerStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
var stats []models.PlayerStat var stats []models.PlayerAdvancedStat
// Filters // Filters
season := c.QueryInt("season", 0) season := c.QueryInt("season", 0)
@@ -61,7 +61,7 @@ func GetAllPlayerStats(db *gorm.DB) fiber.Handler {
} }
// Build query // Build query
query := db.Model(&models.PlayerStat{}) query := db.Model(&models.PlayerAdvancedStat{})
if season != 0 { if season != 0 {
query = query.Where("season = ?", season) query = query.Where("season = ?", season)
+98
View File
@@ -0,0 +1,98 @@
// FetchPlayerTotalStats godoc
// @Summary Fetch player total stats from external API
// @Description Imports totals data and stores or updates in DB
// @Tags PlayerTotals
// @Accept json
// @Produce json
// @Param season query int false "Season (e.g. 2000)"
// @Success 200 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/playertotals/fetch [get]
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/services"
"gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/models"
)
func FetchPlayerTotalStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
season := c.QueryInt("season", 2025)
err := services.FetchAndStorePlayerTotalStats(db, season)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"message": "Player total stats fetched and saved."})
}
}
// GetPlayerTotalStats godoc
// @Summary Get player total stats
// @Description Filter and paginate player totals
// @Tags PlayerTotals
// @Accept json
// @Produce json
// @Param season query int false "Season (e.g. 2000)"
// @Param team query string false "Team abbreviation (e.g. LAL)"
// @Param playerId query string false "Player ID (e.g. greenac01)"
// @Param page query int false "Page number" default(1)
// @Param pageSize query int false "Page size" default(20)
// @Param sortBy query string false "Field to sort by (e.g. points, assists)"
// @Param ascending query bool false "Sort ascending (default false)"
// @Success 200 {object} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Router /api/playertotals [get]
func GetPlayerTotalStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
var stats []models.PlayerTotalStat
season := c.QueryInt("season", 0)
team := c.Query("team")
playerId := c.Query("playerId")
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("pageSize", 20)
sortBy := c.Query("sortBy", "points")
ascending := c.QueryBool("ascending", false)
offset := (page - 1) * pageSize
order := sortBy + " DESC"
if ascending {
order = sortBy + " ASC"
}
query := db.Model(&models.PlayerTotalStat{})
if season != 0 {
query = query.Where("season = ?", season)
}
if team != "" {
query = query.Where("team = ?", team)
}
if playerId != "" {
query = query.Where("player_id = ?", playerId)
}
var total int64
query.Count(&total)
err := query.Order(order).Limit(pageSize).Offset(offset).Find(&stats).Error
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{
"data": stats,
"pagination": fiber.Map{
"total": total,
"page": page,
"pageSize": pageSize,
"pages": (total + int64(pageSize) - 1) / int64(pageSize),
},
})
}
}
+80 -1
View File
@@ -15,7 +15,7 @@ const docTemplate = `{
"host": "{{.Host}}", "host": "{{.Host}}",
"basePath": "{{.BasePath}}", "basePath": "{{.BasePath}}",
"paths": { "paths": {
"/api/playerstats": { "/api/playeradvancedstats": {
"get": { "get": {
"description": "Returns filtered and paginated player stats", "description": "Returns filtered and paginated player stats",
"consumes": [ "consumes": [
@@ -84,6 +84,85 @@ const docTemplate = `{
} }
} }
} }
},
"/api/playertotals": {
"get": {
"description": "Filter and paginate player totals",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PlayerTotals"
],
"summary": "Get player total stats",
"parameters": [
{
"type": "integer",
"description": "Season (e.g. 2000)",
"name": "season",
"in": "query"
},
{
"type": "string",
"description": "Team abbreviation (e.g. LAL)",
"name": "team",
"in": "query"
},
{
"type": "string",
"description": "Player ID (e.g. greenac01)",
"name": "playerId",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 20,
"description": "Page size",
"name": "pageSize",
"in": "query"
},
{
"type": "string",
"description": "Field to sort by (e.g. points, assists)",
"name": "sortBy",
"in": "query"
},
{
"type": "boolean",
"description": "Sort ascending (default false)",
"name": "ascending",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
} }
} }
}` }`
+80 -1
View File
@@ -4,7 +4,7 @@
"contact": {} "contact": {}
}, },
"paths": { "paths": {
"/api/playerstats": { "/api/playeradvancedstats": {
"get": { "get": {
"description": "Returns filtered and paginated player stats", "description": "Returns filtered and paginated player stats",
"consumes": [ "consumes": [
@@ -73,6 +73,85 @@
} }
} }
} }
},
"/api/playertotals": {
"get": {
"description": "Filter and paginate player totals",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"PlayerTotals"
],
"summary": "Get player total stats",
"parameters": [
{
"type": "integer",
"description": "Season (e.g. 2000)",
"name": "season",
"in": "query"
},
{
"type": "string",
"description": "Team abbreviation (e.g. LAL)",
"name": "team",
"in": "query"
},
{
"type": "string",
"description": "Player ID (e.g. greenac01)",
"name": "playerId",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 20,
"description": "Page size",
"name": "pageSize",
"in": "query"
},
{
"type": "string",
"description": "Field to sort by (e.g. points, assists)",
"name": "sortBy",
"in": "query"
},
{
"type": "boolean",
"description": "Sort ascending (default false)",
"name": "ascending",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
} }
} }
} }
+54 -1
View File
@@ -1,7 +1,7 @@
info: info:
contact: {} contact: {}
paths: paths:
/api/playerstats: /api/playeradvancedstats:
get: get:
consumes: consumes:
- application/json - application/json
@@ -48,4 +48,57 @@ paths:
summary: Get player stats summary: Get player stats
tags: tags:
- PlayerStats - PlayerStats
/api/playertotals:
get:
consumes:
- application/json
description: Filter and paginate player totals
parameters:
- description: Season (e.g. 2000)
in: query
name: season
type: integer
- description: Team abbreviation (e.g. LAL)
in: query
name: team
type: string
- description: Player ID (e.g. greenac01)
in: query
name: playerId
type: string
- default: 1
description: Page number
in: query
name: page
type: integer
- default: 20
description: Page size
in: query
name: pageSize
type: integer
- description: Field to sort by (e.g. points, assists)
in: query
name: sortBy
type: string
- description: Sort ascending (default false)
in: query
name: ascending
type: boolean
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get player total stats
tags:
- PlayerTotals
swagger: "2.0" swagger: "2.0"
+29 -5
View File
@@ -2,6 +2,8 @@ package main
import ( import (
"log" "log"
"time"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/config" "github.com/nprasad2077/NBA_Go/config"
"github.com/nprasad2077/NBA_Go/routes" "github.com/nprasad2077/NBA_Go/routes"
@@ -12,22 +14,44 @@ import (
func main() { func main() {
app := fiber.New() app := fiber.New()
db := config.InitDB() db := config.InitDB()
// Automatically fetch player stats on startup // Automatically fetch player stats on startup
go func() { go func() {
if err := services.FetchAndStorePlayerStats(db, 2025); err != nil { for season := 2020; season <= 2025; season++ {
log.Println("Initial fetch failed:", err) if err := services.FetchAndStorePlayerAdvancedStats(db, season); err != nil {
} else { log.Printf("Fetch failed for player advanced season %d: %v\n", season, err)
log.Println("Initial player stats fetch successful.") } else {
log.Printf("Fetch successful for player advanced season %d\n", season)
}
time.Sleep(2 * time.Second) // optional delay
} }
log.Printf("player advanced Import Success")
}()
go func() {
for season := 2020; season <= 2025; season++ {
if err := services.FetchAndStorePlayerTotalStats(db, season); err != nil {
log.Printf("Fetch failed for player totals season %d: %v\n", season, err)
} else {
log.Printf("Fetch successful for player totals season %d\n", season)
}
time.Sleep(1 * time.Second) // optional delay
}
log.Printf("player totals Import Success")
}() }()
routes.RegisterPlayerRoutes(app, db)
routes.RegisterPlayerAdvancedRoutes(app, db)
routes.RegisterPlayerTotalRoutes(app, db)
app.Get("/swagger/*", fiberswagger.WrapHandler) app.Get("/swagger/*", fiberswagger.WrapHandler)
app.Use(logger.New())
app.Listen(":3001") app.Listen(":3001")
} }
+36
View File
@@ -0,0 +1,36 @@
package models
import "gorm.io/gorm"
type PlayerAdvancedStat 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"`
}
+39
View File
@@ -0,0 +1,39 @@
package models
import "gorm.io/gorm"
type PlayerTotalStat struct {
gorm.Model
ExternalID int `json:"id"`
PlayerID string `gorm:"not null;uniqueIndex:idx_total_player_season_team" json:"playerId"`
PlayerName string `json:"playerName"`
Position string `json:"position"`
Age int `json:"age"`
Games int `json:"games"`
GamesStarted int `json:"gamesStarted"`
MinutesPG float64 `json:"minutesPg"`
FieldGoals int `json:"fieldGoals"`
FieldAttempts int `json:"fieldAttempts"`
FieldPercent float64 `json:"fieldPercent"`
ThreeFG int `json:"threeFg"`
ThreeAttempts int `json:"threeAttempts"`
ThreePercent float64 `json:"threePercent"`
TwoFG int `json:"twoFg"`
TwoAttempts int `json:"twoAttempts"`
TwoPercent float64 `json:"twoPercent"`
EffectFGPercent float64 `json:"effectFgPercent"`
FT int `json:"ft"`
FTAttempts int `json:"ftAttempts"`
FTPercent float64 `json:"ftPercent"`
OffensiveRB int `json:"offensiveRb"`
DefensiveRB int `json:"defensiveRb"`
TotalRB int `json:"totalRb"`
Assists int `json:"assists"`
Steals int `json:"steals"`
Blocks int `json:"blocks"`
Turnovers int `json:"turnovers"`
PersonalFouls int `json:"personalFouls"`
Points int `json:"points"`
Team string `gorm:"not null;uniqueIndex:idx_total_player_season_team" json:"team"`
Season int `gorm:"not null;uniqueIndex:idx_total_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 RegisterPlayerAdvancedRoutes(app *fiber.App, db *gorm.DB) {
api := app.Group("/api/playeradvancedstats")
api.Get("/fetch", controllers.FetchPlayerAdvancedStats(db))
api.Get("/", controllers.GetAllAdvancedPlayerStats(db)) // ✅ Add this line
}
-14
View File
@@ -1,14 +0,0 @@
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
}
+14
View File
@@ -0,0 +1,14 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/controllers"
"gorm.io/gorm"
)
func RegisterPlayerTotalRoutes(app *fiber.App, db *gorm.DB) {
api := app.Group("/api/playertotals")
api.Get("/fetch", controllers.FetchPlayerTotalStats(db))
api.Get("/", controllers.GetPlayerTotalStats(db))
}
@@ -10,15 +10,15 @@ import (
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
func FetchAndStorePlayerStats(db *gorm.DB, season int) error { func FetchAndStorePlayerAdvancedStats(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) url := fmt.Sprintf("http://rest.nbaapi.com/api/PlayerDataAdvanced/query?season=%d&sortBy=Points&ascending=false&pageNumber=1&pageSize=1000", season)
body, err := utils.GetJSON(url) body, err := utils.GetJSON(url)
if err != nil { if err != nil {
return err return err
} }
var stats []models.PlayerStat var stats []models.PlayerAdvancedStat
if err := json.Unmarshal(body, &stats); err != nil { if err := json.Unmarshal(body, &stats); err != nil {
return err return err
} }
+51
View File
@@ -0,0 +1,51 @@
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 FetchAndStorePlayerTotalStats(db *gorm.DB, season int) error {
url := fmt.Sprintf(
"http://rest.nbaapi.com/api/PlayerDataTotals/query?season=%d&sortBy=PlayerName&ascending=true&pageNumber=1&pageSize=1000",
season,
)
body, err := utils.GetJSON(url)
if err != nil {
return err
}
var stats []models.PlayerTotalStat
if err := json.Unmarshal(body, &stats); err != nil {
return err
}
for _, stat := range stats {
stat.Season = season
err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "player_id"}, {Name: "season"}, {Name: "team"}},
DoUpdates: clause.AssignmentColumns([]string{
"player_name", "position", "age", "games", "games_started",
"minutes_pg", "field_goals", "field_attempts", "field_percent",
"three_fg", "three_attempts", "three_percent", "two_fg", "two_attempts",
"two_percent", "effect_fg_percent", "ft", "ft_attempts", "ft_percent",
"offensive_rb", "defensive_rb", "total_rb", "assists", "steals",
"blocks", "turnovers", "personal_fouls", "points",
}),
}).Create(&stat).Error
if err != nil {
log.Printf("Failed to upsert PlayerTotalStat for playerId %s: %v", stat.PlayerID, err)
}
}
return nil
}