This commit is contained in:
Ravi Prasad
2025-06-03 21:29:16 -05:00
parent a3225b1803
commit 24ec6675c9
7 changed files with 108 additions and 107 deletions
+27 -27
View File
@@ -1,35 +1,35 @@
package config package config
import ( import (
"log" "github.com/nprasad2077/NBA_Go/models"
"gorm.io/driver/sqlite" "github.com/nprasad2077/NBA_Go/utils/metrics"
"gorm.io/gorm" "gorm.io/driver/sqlite"
"github.com/nprasad2077/NBA_Go/models" "gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/utils/metrics" "log"
) )
func InitDB(shouldMigrate bool) *gorm.DB { func InitDB(shouldMigrate bool) *gorm.DB {
db, err := gorm.Open(sqlite.Open("/app/data/nba.db"), &gorm.Config{}) db, err := gorm.Open(sqlite.Open("/app/data/nba.db"), &gorm.Config{})
if err != nil { if err != nil {
log.Fatalf("failed to connect database: %v", err) log.Fatalf("failed to connect database: %v", err)
} }
metrics.DBOperationsTotal.WithLabelValues("connect", "database").Inc() metrics.DBOperationsTotal.WithLabelValues("connect", "database").Inc()
if shouldMigrate { if shouldMigrate {
if err := db.AutoMigrate(&models.PlayerAdvancedStat{}); err != nil { if err := db.AutoMigrate(&models.PlayerAdvancedStat{}); err != nil {
log.Fatalf("migrate PlayerAdvancedStat: %v", err) log.Fatalf("migrate PlayerAdvancedStat: %v", err)
} }
if err := db.AutoMigrate(&models.PlayerTotalStat{}); err != nil { if err := db.AutoMigrate(&models.PlayerTotalStat{}); err != nil {
log.Fatalf("migrate PlayerTotalStat: %v", err) log.Fatalf("migrate PlayerTotalStat: %v", err)
} }
if err := db.AutoMigrate(&models.PlayerShotChart{}); err != nil { if err := db.AutoMigrate(&models.PlayerShotChart{}); err != nil {
log.Fatalf("migrate PlayerShotChart: %v", err) log.Fatalf("migrate PlayerShotChart: %v", err)
} }
if err := db.AutoMigrate(&models.APIKey{}); err != nil { if err := db.AutoMigrate(&models.APIKey{}); err != nil {
log.Fatalf("migrate APIKey: %v", err) log.Fatalf("migrate APIKey: %v", err)
} }
metrics.DBOperationsTotal.WithLabelValues("migrate", "database").Inc() metrics.DBOperationsTotal.WithLabelValues("migrate", "database").Inc()
} }
return db return db
} }
+11 -11
View File
@@ -44,18 +44,18 @@ type AdvancedStatsResponse struct {
// @Failure 400,500 {object} map[string]string // @Failure 400,500 {object} map[string]string
// @Router /api/playeradvancedstats/scrape [get] // @Router /api/playeradvancedstats/scrape [get]
func ScrapePlayerAdvancedStats(db *gorm.DB) fiber.Handler { func ScrapePlayerAdvancedStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
season := c.QueryInt("season", 0) season := c.QueryInt("season", 0)
if season == 0 { if season == 0 {
return c.Status(400).JSON(fiber.Map{"error":"season is required"}) return c.Status(400).JSON(fiber.Map{"error": "season is required"})
} }
isPlayoff := c.QueryBool("isPlayoff", false) isPlayoff := c.QueryBool("isPlayoff", false)
if err := services.FetchAndStorePlayerAdvancedScrapedStats(db, season, isPlayoff); err != nil { if err := services.FetchAndStorePlayerAdvancedScrapedStats(db, season, isPlayoff); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()}) return c.Status(500).JSON(fiber.Map{"error": err.Error()})
} }
return c.JSON(fiber.Map{"message":"scrape+store complete"}) return c.JSON(fiber.Map{"message": "scrape+store complete"})
} }
} }
// GetAllAdvancedPlayerStats godoc // GetAllAdvancedPlayerStats godoc
+41 -39
View File
@@ -1,10 +1,10 @@
package controllers package controllers
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/models" "github.com/nprasad2077/NBA_Go/models"
"github.com/nprasad2077/NBA_Go/services" "github.com/nprasad2077/NBA_Go/services"
"gorm.io/gorm" "gorm.io/gorm"
) )
// FetchPlayerShotChartAPI godoc // FetchPlayerShotChartAPI godoc
@@ -32,7 +32,9 @@ import (
// ScrapePlayerShotChart godoc // ScrapePlayerShotChart godoc
// @Summary Scrape a player's shot-chart from BR website // @Summary Scrape a player's shot-chart from BR website
// @Description Scrapes seasons [startSeason…endSeason] for the given playerId // @Description Scrapes seasons [startSeason…endSeason] for the given playerId
// (playerName is auto-detected). //
// (playerName is auto-detected).
//
// @Tags PlayerShotChart // @Tags PlayerShotChart
// @Accept json // @Accept json
// @Produce json // @Produce json
@@ -43,25 +45,25 @@ import (
// @Failure 400,500 {object} map[string]string // @Failure 400,500 {object} map[string]string
// @Router /api/playershotchart/scrape [get] // @Router /api/playershotchart/scrape [get]
func ScrapePlayerShotChart(db *gorm.DB) fiber.Handler { func ScrapePlayerShotChart(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
pid := c.Query("playerId") pid := c.Query("playerId")
start := c.QueryInt("startSeason", 0) start := c.QueryInt("startSeason", 0)
end := c.QueryInt("endSeason", 0) end := c.QueryInt("endSeason", 0)
if pid == "" || start == 0 || end == 0 { if pid == "" || start == 0 || end == 0 {
return c.Status(400).JSON(fiber.Map{ return c.Status(400).JSON(fiber.Map{
"error": "playerId, startSeason and endSeason are required", "error": "playerId, startSeason and endSeason are required",
}) })
} }
if start < end { if start < end {
return c.Status(400).JSON(fiber.Map{ return c.Status(400).JSON(fiber.Map{
"error": "startSeason must be >= endSeason", "error": "startSeason must be >= endSeason",
}) })
} }
if err := services.FetchAndStoreShotChartScrapedForPlayer(db, pid, start, end); err != nil { if err := services.FetchAndStoreShotChartScrapedForPlayer(db, pid, start, end); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()}) return c.Status(500).JSON(fiber.Map{"error": err.Error()})
} }
return c.JSON(fiber.Map{"message": "Shot chart scraped and saved for " + pid}) return c.JSON(fiber.Map{"message": "Shot chart scraped and saved for " + pid})
} }
} }
// GetPlayerShotChart godoc // GetPlayerShotChart godoc
@@ -77,21 +79,21 @@ func ScrapePlayerShotChart(db *gorm.DB) fiber.Handler {
// @Failure 500 {object} map[string]string // @Failure 500 {object} map[string]string
// @Router /api/playershotchart [get] // @Router /api/playershotchart [get]
func GetPlayerShotChart(db *gorm.DB) fiber.Handler { func GetPlayerShotChart(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
var shots []models.PlayerShotChart var shots []models.PlayerShotChart
query := db.Model(&models.PlayerShotChart{}) query := db.Model(&models.PlayerShotChart{})
if pid := c.Query("playerId"); pid != "" { if pid := c.Query("playerId"); pid != "" {
query = query.Where("player_id = ?", pid) query = query.Where("player_id = ?", pid)
} }
if s := c.QueryInt("season", 0); s != 0 { if s := c.QueryInt("season", 0); s != 0 {
query = query.Where("season = ?", s) query = query.Where("season = ?", s)
} }
if err := query.Find(&shots).Error; err != nil { if err := query.Find(&shots).Error; err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()}) return c.Status(500).JSON(fiber.Map{"error": err.Error()})
} }
return c.JSON(shots) return c.JSON(shots)
} }
} }
+14 -15
View File
@@ -14,9 +14,9 @@ package controllers
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/models"
"github.com/nprasad2077/NBA_Go/services" "github.com/nprasad2077/NBA_Go/services"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/models"
) )
// func FetchPlayerTotalStats(db *gorm.DB) fiber.Handler { // func FetchPlayerTotalStats(db *gorm.DB) fiber.Handler {
@@ -42,23 +42,22 @@ import (
// @Failure 400,500 {object} map[string]string // @Failure 400,500 {object} map[string]string
// @Router /api/playertotals/scrape [get] // @Router /api/playertotals/scrape [get]
func ScrapePlayerTotalStats(db *gorm.DB) fiber.Handler { func ScrapePlayerTotalStats(db *gorm.DB) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
season := c.QueryInt("season", 0) season := c.QueryInt("season", 0)
if season == 0 { if season == 0 {
return c.Status(400).JSON(fiber.Map{"error":"season is required"}) return c.Status(400).JSON(fiber.Map{"error": "season is required"})
} }
isPlayoff := c.QueryBool("isPlayoff", false) isPlayoff := c.QueryBool("isPlayoff", false)
if err := services.FetchAndStorePlayerTotalScrapedStats(db, season, isPlayoff); err != nil { if err := services.FetchAndStorePlayerTotalScrapedStats(db, season, isPlayoff); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()}) return c.Status(500).JSON(fiber.Map{"error": err.Error()})
} }
return c.JSON(fiber.Map{"message":"scrapestore complete"}) return c.JSON(fiber.Map{"message": "scrapestore complete"})
} }
} }
// GetPlayerTotalStats godoc // GetPlayerTotalStats godoc
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Summary Get player total stats // @Summary Get player total stats
// @Description Filter and paginate player totals // @Description Filter and paginate player totals
// @Tags PlayerTotals // @Tags PlayerTotals
@@ -128,4 +127,4 @@ func GetPlayerTotalStats(db *gorm.DB) fiber.Handler {
}, },
}) })
} }
} }
+1 -1
View File
@@ -76,4 +76,4 @@ func TestGetPlayerAdvancedStats(t *testing.T) {
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
assert.Contains(t, string(body), tc.wantSubstring, tc.name) assert.Contains(t, string(body), tc.wantSubstring, tc.name)
} }
} }
+1 -1
View File
@@ -148,4 +148,4 @@ func main() {
fmt.Printf("Total Time: %v\n", totalTime) fmt.Printf("Total Time: %v\n", totalTime)
fmt.Printf("Average Response Time: %v\n", avgDuration) fmt.Printf("Average Response Time: %v\n", avgDuration)
fmt.Printf("Requests Per Second: %.2f\n", requestsPerSecond) fmt.Printf("Requests Per Second: %.2f\n", requestsPerSecond)
} }
+13 -13
View File
@@ -2,25 +2,25 @@
package utils package utils
import ( import (
"log" "log"
"math/rand" "math/rand"
"time" "time"
) )
func init() { func init() {
// Seed the RNG once when the package is initialized // Seed the RNG once when the package is initialized
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
} }
// SleepWithJitter sleeps for the given base duration plus or minus 25% random jitter. // SleepWithJitter sleeps for the given base duration plus or minus 25% random jitter.
// It also logs the actual sleep duration so you can verify it fired correctly. // It also logs the actual sleep duration so you can verify it fired correctly.
func SleepWithJitter(base time.Duration) { func SleepWithJitter(base time.Duration) {
half := base / 2 half := base / 2
// Random value in [0, half) // Random value in [0, half)
randOffset := time.Duration(rand.Int63n(int64(half))) randOffset := time.Duration(rand.Int63n(int64(half)))
// Shift to center jitter around zero: [-half/2, +half/2) // Shift to center jitter around zero: [-half/2, +half/2)
delta := randOffset - half/2 delta := randOffset - half/2
actual := base + delta actual := base + delta
log.Printf("⏱️ Sleeping for %v (base=%v, jitter=%v)", actual, base, delta) log.Printf("⏱️ Sleeping for %v (base=%v, jitter=%v)", actual, base, delta)
time.Sleep(actual) time.Sleep(actual)
} }