mirror of
https://github.com/nprasad2077/NBA_Go.git
synced 2026-09-22 14:05:13 +00:00
shot chart create
This commit is contained in:
@@ -21,6 +21,7 @@ func InitDB() *gorm.DB {
|
|||||||
// Auto migrate models
|
// Auto migrate models
|
||||||
db.AutoMigrate(&models.PlayerAdvancedStat{})
|
db.AutoMigrate(&models.PlayerAdvancedStat{})
|
||||||
db.AutoMigrate(&models.PlayerTotalStat{})
|
db.AutoMigrate(&models.PlayerTotalStat{})
|
||||||
|
db.AutoMigrate(&models.PlayerShotChart{})
|
||||||
|
|
||||||
db.AutoMigrate(&models.APIKey{})
|
db.AutoMigrate(&models.APIKey{})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// NBA_Go/controllers/player_shot_chart_controller.go
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/nprasad2077/NBA_Go/models"
|
||||||
|
"github.com/nprasad2077/NBA_Go/services"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FetchPlayerShotChart godoc
|
||||||
|
// @Summary Fetch a single player's shot-chart data from external API
|
||||||
|
// @Description Imports shot-chart data for the given playerId and stores/updates in DB
|
||||||
|
// @Tags PlayerShotChart
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param playerId query string true "Player ID (e.g., hardeja01)"
|
||||||
|
// @Success 200 {object} map[string]string
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /api/playershotchart/fetch [get]
|
||||||
|
func FetchPlayerShotChart(db *gorm.DB) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
pid := c.Query("playerId")
|
||||||
|
if pid == "" {
|
||||||
|
return c.Status(400).JSON(fiber.Map{
|
||||||
|
"error": "playerId query parameter is required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := services.FetchAndStoreShotChartForPlayer(db, pid); err != nil {
|
||||||
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return c.JSON(fiber.Map{"message": "Shot chart for " + pid + " fetched and saved."})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlayerShotChart godoc
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Summary Get shot-chart data
|
||||||
|
// @Description Returns shot-chart points, optionally filtered by playerId and/or season
|
||||||
|
// @Tags PlayerShotChart
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param playerId query string false "Player ID (e.g., hardeja01)"
|
||||||
|
// @Param season query int false "Season (e.g., 2023)"
|
||||||
|
// @Success 200 {array} models.PlayerShotChart
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /api/playershotchart [get]
|
||||||
|
func GetPlayerShotChart(db *gorm.DB) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var shots []models.PlayerShotChart
|
||||||
|
|
||||||
|
// Start with the base model
|
||||||
|
query := db.Model(&models.PlayerShotChart{})
|
||||||
|
|
||||||
|
// Optional filter: playerId
|
||||||
|
if pid := c.Query("playerId"); pid != "" {
|
||||||
|
query = query.Where("player_id = ?", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional filter: season
|
||||||
|
if s := c.QueryInt("season", 0); s != 0 {
|
||||||
|
query = query.Where("season = ?", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
if err := query.Find(&shots).Error; err != nil {
|
||||||
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(shots)
|
||||||
|
}
|
||||||
|
}
|
||||||
+163
@@ -106,6 +106,113 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/playershotchart": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns shot-chart points, optionally filtered by playerId and/or season",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"PlayerShotChart"
|
||||||
|
],
|
||||||
|
"summary": "Get shot-chart data",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Player ID (e.g., hardeja01)",
|
||||||
|
"name": "playerId",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Season (e.g., 2023)",
|
||||||
|
"name": "season",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.PlayerShotChart"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/playershotchart/fetch": {
|
||||||
|
"get": {
|
||||||
|
"description": "Imports shot-chart data for the given playerId and stores/updates in DB",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"PlayerShotChart"
|
||||||
|
],
|
||||||
|
"summary": "Fetch a single player's shot-chart data from external API",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Player ID (e.g., hardeja01)",
|
||||||
|
"name": "playerId",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/playertotals": {
|
"/api/playertotals": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -320,6 +427,62 @@ const docTemplate = `{
|
|||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"models.PlayerShotChart": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"distanceFt": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lead": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"left": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"opponent": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"opponentTeamScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"playerId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"playerName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"qtr": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"season": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"shotType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"teamScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"timeRemaining": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"top": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -103,6 +103,113 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/playershotchart": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns shot-chart points, optionally filtered by playerId and/or season",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"PlayerShotChart"
|
||||||
|
],
|
||||||
|
"summary": "Get shot-chart data",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Player ID (e.g., hardeja01)",
|
||||||
|
"name": "playerId",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Season (e.g., 2023)",
|
||||||
|
"name": "season",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.PlayerShotChart"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/playershotchart/fetch": {
|
||||||
|
"get": {
|
||||||
|
"description": "Imports shot-chart data for the given playerId and stores/updates in DB",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"PlayerShotChart"
|
||||||
|
],
|
||||||
|
"summary": "Fetch a single player's shot-chart data from external API",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Player ID (e.g., hardeja01)",
|
||||||
|
"name": "playerId",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/playertotals": {
|
"/api/playertotals": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -317,6 +424,62 @@
|
|||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"models.PlayerShotChart": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"distanceFt": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lead": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"left": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"opponent": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"opponentTeamScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"playerId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"playerName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"qtr": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"season": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"shotType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"teamScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"timeRemaining": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"top": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -81,6 +81,43 @@ definitions:
|
|||||||
winSharesPer:
|
winSharesPer:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
models.PlayerShotChart:
|
||||||
|
properties:
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
distanceFt:
|
||||||
|
type: integer
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
lead:
|
||||||
|
type: boolean
|
||||||
|
left:
|
||||||
|
type: integer
|
||||||
|
opponent:
|
||||||
|
type: string
|
||||||
|
opponentTeamScore:
|
||||||
|
type: integer
|
||||||
|
playerId:
|
||||||
|
type: string
|
||||||
|
playerName:
|
||||||
|
type: string
|
||||||
|
qtr:
|
||||||
|
type: string
|
||||||
|
result:
|
||||||
|
type: boolean
|
||||||
|
season:
|
||||||
|
type: integer
|
||||||
|
shotType:
|
||||||
|
type: string
|
||||||
|
team:
|
||||||
|
type: string
|
||||||
|
teamScore:
|
||||||
|
type: integer
|
||||||
|
timeRemaining:
|
||||||
|
type: string
|
||||||
|
top:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
description: Stats service with API-key auth
|
description: Stats service with API-key auth
|
||||||
@@ -147,6 +184,77 @@ paths:
|
|||||||
summary: Get player advanced stats
|
summary: Get player advanced stats
|
||||||
tags:
|
tags:
|
||||||
- PlayerStats
|
- PlayerStats
|
||||||
|
/api/playershotchart:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Returns shot-chart points, optionally filtered by playerId and/or
|
||||||
|
season
|
||||||
|
parameters:
|
||||||
|
- description: Player ID (e.g., hardeja01)
|
||||||
|
in: query
|
||||||
|
name: playerId
|
||||||
|
type: string
|
||||||
|
- description: Season (e.g., 2023)
|
||||||
|
in: query
|
||||||
|
name: season
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.PlayerShotChart'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Get shot-chart data
|
||||||
|
tags:
|
||||||
|
- PlayerShotChart
|
||||||
|
/api/playershotchart/fetch:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Imports shot-chart data for the given playerId and stores/updates
|
||||||
|
in DB
|
||||||
|
parameters:
|
||||||
|
- description: Player ID (e.g., hardeja01)
|
||||||
|
in: query
|
||||||
|
name: playerId
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Fetch a single player's shot-chart data from external API
|
||||||
|
tags:
|
||||||
|
- PlayerShotChart
|
||||||
/api/playertotals:
|
/api/playertotals:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -48,3 +48,13 @@ func importPlayerPlayoffs(db *gorm.DB) {
|
|||||||
time.Sleep(1500 * time.Millisecond)
|
time.Sleep(1500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// importPlayerShotChart fetches shot-charts for every known player
|
||||||
|
func importPlayerShotChart(db *gorm.DB) {
|
||||||
|
const firstID = "hardeja01"
|
||||||
|
log.Printf("▶️ importing shot chart for player %s…", firstID)
|
||||||
|
if err := services.FetchAndStoreShotChartForPlayer(db, firstID); err != nil {
|
||||||
|
log.Printf("shot chart import failed for %s: %v", firstID, err)
|
||||||
|
}
|
||||||
|
// you can add more IDs here or just rely on the API endpoint after
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ func main() {
|
|||||||
log.Println("🎉 Player Totals Import completed successfully")
|
log.Println("🎉 Player Totals Import completed successfully")
|
||||||
importPlayerPlayoffs(db)
|
importPlayerPlayoffs(db)
|
||||||
log.Println("🎉 Player Totals Playoffs Import completed successfully")
|
log.Println("🎉 Player Totals Playoffs Import completed successfully")
|
||||||
|
importPlayerShotChart(db)
|
||||||
|
log.Println("🎉 Player Shot Chart Import Completed Successfully ")
|
||||||
log.Println("🎉 Import completed successfully")
|
log.Println("🎉 Import completed successfully")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -80,6 +82,7 @@ func main() {
|
|||||||
app.Use(middleware.APIKeyAuth(db))
|
app.Use(middleware.APIKeyAuth(db))
|
||||||
routes.RegisterPlayerAdvancedRoutes(app, db)
|
routes.RegisterPlayerAdvancedRoutes(app, db)
|
||||||
routes.RegisterPlayerTotalRoutes(app, db)
|
routes.RegisterPlayerTotalRoutes(app, db)
|
||||||
|
routes.RegisterPlayerShotChartRoutes(app, db)
|
||||||
|
|
||||||
/* ---------- START & SHUTDOWN ---------- */
|
/* ---------- START & SHUTDOWN ---------- */
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import "gorm.io/gorm"
|
|||||||
|
|
||||||
type PlayerShotChart struct {
|
type PlayerShotChart struct {
|
||||||
gorm.Model `swaggerignore:"true"`
|
gorm.Model `swaggerignore:"true"`
|
||||||
ExternalID int `json:"id"`
|
ExternalID int `gorm:"not null;uniqueIndex:idx_shotchart_player_external" json:"id"`
|
||||||
PlayerID string `gorm:"not null;index:idx_shotchart_player_external,unique" json:"playerId"`
|
PlayerID string `gorm:"not null;uniqueIndex:idx_shotchart_player_external" json:"playerId"`
|
||||||
PlayerName string `json:"playerName"`
|
PlayerName string `json:"playerName"`
|
||||||
Top int `json:"top"`
|
Top int `json:"top"`
|
||||||
Left int `json:"left"`
|
Left int `json:"left"`
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/nprasad2077/NBA_Go/controllers"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterPlayerShotChartRoutes(app *fiber.App, db *gorm.DB) {
|
||||||
|
api := app.Group("/api/playershotchart")
|
||||||
|
api.Get("/fetch", controllers.FetchPlayerShotChart(db))
|
||||||
|
api.Get("/", controllers.GetPlayerShotChart(db))
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nprasad2077/NBA_Go/models"
|
||||||
|
"github.com/nprasad2077/NBA_Go/utils"
|
||||||
|
"github.com/nprasad2077/NBA_Go/utils/metrics"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FetchAndStoreShotChartForPlayer fetches a single player's shot chart, parses
|
||||||
|
// each shot's date into a season, and upserts into DB.
|
||||||
|
func FetchAndStoreShotChartForPlayer(db *gorm.DB, playerId string) error {
|
||||||
|
metrics.DBOperationsTotal.WithLabelValues("fetch", "player_shot_chart").Inc()
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://rest.nbaapi.com/api/ShotChartData/playerid/%s", playerId)
|
||||||
|
body, err := utils.GetJSON(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var shots []models.PlayerShotChart
|
||||||
|
if err := json.Unmarshal(body, &shots); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, shot := range shots {
|
||||||
|
// parse date "Feb 11, 2023" → time.Time
|
||||||
|
if t, err := time.Parse("Jan 2, 2006", shot.Date); err == nil {
|
||||||
|
shot.Season = t.Year()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "player_id"}, {Name: "external_id"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{
|
||||||
|
"player_name", "top", "left", "date", "qtr",
|
||||||
|
"time_remaining", "result", "shot_type", "distance_ft",
|
||||||
|
"lead", "team_score", "opponent_team_score", "opponent",
|
||||||
|
"team", "season",
|
||||||
|
}),
|
||||||
|
}).Create(&shot).Error
|
||||||
|
|
||||||
|
metrics.DBOperationsTotal.WithLabelValues("store", "player_shot_chart").Inc()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to upsert shot chart for %s id=%d: %v",
|
||||||
|
shot.PlayerID, shot.ExternalID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAndStoreAllPlayerShotCharts loads every distinct playerId from your stats
|
||||||
|
// tables and invokes the per-player fetch.
|
||||||
|
func FetchAndStoreAllPlayerShotCharts(db *gorm.DB) error {
|
||||||
|
var playerIds []string
|
||||||
|
|
||||||
|
// gather from total stats (could also include advanced stats)
|
||||||
|
db.Model(&models.PlayerTotalStat{}).
|
||||||
|
Distinct("player_id").
|
||||||
|
Pluck("player_id", &playerIds)
|
||||||
|
|
||||||
|
for _, pid := range playerIds {
|
||||||
|
if err := FetchAndStoreShotChartForPlayer(db, pid); err != nil {
|
||||||
|
log.Printf("Error importing shot chart for %s: %v", pid, err)
|
||||||
|
}
|
||||||
|
// throttle
|
||||||
|
time.Sleep(1100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user