This commit is contained in:
2026-09-10 13:49:36 -05:00
parent 7a5894d9cb
commit 768f44f669
8 changed files with 13805 additions and 14 deletions
+1
View File
@@ -48,3 +48,4 @@ docs/digital_ocean/digitaal ocean llms.txt
.docs .docs
NBA_Go NBA_Go
.docs
+6
View File
@@ -32,6 +32,7 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
fiberswagger "github.com/swaggo/fiber-swagger" fiberswagger "github.com/swaggo/fiber-swagger"
@@ -100,6 +101,11 @@ func main() {
// — CORS Allow ALL origins (development) — // — CORS Allow ALL origins (development) —
app.Use(cors.New()) app.Use(cors.New())
// — Global Brotli/Gzip Compression (shrinks transatlantic payload bytes by 70-85%) —
app.Use(compress.New(compress.Config{
Level: compress.LevelBestSpeed,
}))
// Middlewares // Middlewares
app.Use(middleware.StructuredLogger()) app.Use(middleware.StructuredLogger())
app.Use(middleware.RateLimiter()) app.Use(middleware.RateLimiter())
+4 -3
View File
@@ -2,14 +2,15 @@ package routes
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/controllers" "github.com/nprasad2077/NBA_Go/controllers"
"github.com/nprasad2077/NBA_Go/utils/middleware"
"gorm.io/gorm"
) )
func RegisterGameRoutes(app *fiber.App, db *gorm.DB) { func RegisterGameRoutes(app *fiber.App, db *gorm.DB) {
// Create a new group for game-related endpoints // Create a new group for game-related endpoints
api := app.Group("/api/games") api := app.Group("/api/games")
// Register the GET endpoint to fetch games // Register the GET endpoint to fetch games with immutable edge caching
api.Get("/", controllers.GetGames(db)) api.Get("/", middleware.EdgeCache(middleware.Immutable, "nba-games"), controllers.GetGames(db))
} }
+6 -4
View File
@@ -2,14 +2,16 @@ package routes
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"github.com/nprasad2077/NBA_Go/controllers" "github.com/nprasad2077/NBA_Go/controllers"
"github.com/nprasad2077/NBA_Go/utils/middleware"
"gorm.io/gorm"
) )
func RegisterPlayerAdvancedRoutes(app *fiber.App, db *gorm.DB) { func RegisterPlayerAdvancedRoutes(app *fiber.App, db *gorm.DB) {
api := app.Group("/api/playeradvancedstats") api := app.Group("/api/playeradvancedstats")
// api.Get("/fetch", controllers.FetchPlayerAdvancedStats(db)) // Scraper trigger - never cached
api.Get("/scrape", controllers.ScrapePlayerAdvancedStats(db)) api.Get("/scrape", middleware.EdgeCache(middleware.NoCache), controllers.ScrapePlayerAdvancedStats(db))
api.Get("/", controllers.GetAllAdvancedPlayerStats(db)) // Public query endpoint - edge cached with stale-while-revalidate
api.Get("/", middleware.EdgeCache(middleware.SemiDynamic, "nba-player-advanced"), controllers.GetAllAdvancedPlayerStats(db))
} }
+5 -3
View File
@@ -3,13 +3,15 @@ package routes
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/controllers" "github.com/nprasad2077/NBA_Go/controllers"
"github.com/nprasad2077/NBA_Go/utils/middleware"
"gorm.io/gorm" "gorm.io/gorm"
) )
// RegisterPlayerShotChartRoutes sets up the shot-chart endpoints // RegisterPlayerShotChartRoutes sets up the shot-chart endpoints
func RegisterPlayerShotChartRoutes(app *fiber.App, db *gorm.DB) { func RegisterPlayerShotChartRoutes(app *fiber.App, db *gorm.DB) {
api := app.Group("/api/playershotchart") api := app.Group("/api/playershotchart")
// api.Get("/fetch", controllers.FetchPlayerShotChartAPI(db)) // Scraper trigger - never cached
api.Get("/scrape", controllers.ScrapePlayerShotChart(db)) api.Get("/scrape", middleware.EdgeCache(middleware.NoCache), controllers.ScrapePlayerShotChart(db))
api.Get("/", controllers.GetPlayerShotChart(db)) // Public shot chart query endpoint - immutable 30-day edge cache
api.Get("/", middleware.EdgeCache(middleware.Immutable, "nba-shot-charts"), controllers.GetPlayerShotChart(db))
} }
+5 -3
View File
@@ -3,13 +3,15 @@ package routes
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/controllers" "github.com/nprasad2077/NBA_Go/controllers"
"github.com/nprasad2077/NBA_Go/utils/middleware"
"gorm.io/gorm" "gorm.io/gorm"
) )
func RegisterPlayerTotalRoutes(app *fiber.App, db *gorm.DB) { func RegisterPlayerTotalRoutes(app *fiber.App, db *gorm.DB) {
api := app.Group("/api/playertotals") api := app.Group("/api/playertotals")
// api.Get("/fetch", controllers.FetchPlayerTotalStats(db)) // Scraper trigger - never cached
api.Get("/scrape", controllers.ScrapePlayerTotalStats(db)) api.Get("/scrape", middleware.EdgeCache(middleware.NoCache), controllers.ScrapePlayerTotalStats(db))
api.Get("/", controllers.GetPlayerTotalStats(db)) // Public query endpoint - edge cached with stale-while-revalidate
api.Get("/", middleware.EdgeCache(middleware.SemiDynamic, "nba-player-totals"), controllers.GetPlayerTotalStats(db))
} }
+13700
View File
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
package middleware
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/gofiber/fiber/v2"
)
// CacheStrategy defines caching profiles for different API data mutability tiers
type CacheStrategy string
const (
// Immutable: For historical completed games, past seasons, and shot charts.
// Edge TTL: 30 Days | Browser TTL: 1 Day
Immutable CacheStrategy = "immutable"
// SemiDynamic: For daily leaderboards, player season averages, and standings.
// Edge TTL: 1 Day | Browser TTL: 1 Minute | Stale-While-Revalidate: 10 Minutes
SemiDynamic CacheStrategy = "semi_dynamic"
// Realtime: For active game telemetry, live quarter stats, and fast-updating data.
// Edge TTL: 10 Seconds | Browser TTL: 5 Seconds | Stale-While-Revalidate: 30 Seconds
Realtime CacheStrategy = "realtime"
// NoCache: For transactional mutations, scrapers, and admin endpoints.
NoCache CacheStrategy = "no_cache"
)
// EdgeCache injects standardized Cloudflare & HTTP caching headers
func EdgeCache(strategy CacheStrategy, tags ...string) fiber.Handler {
return func(c *fiber.Ctx) error {
// Only cache idempotent read requests (GET / HEAD)
if c.Method() != fiber.MethodGet && c.Method() != fiber.MethodHead {
c.Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
c.Set("Pragma", "no-cache")
c.Set("Expires", "0")
return c.Next()
}
switch strategy {
case Immutable:
// Served directly by Cloudflare US Edge for 30 days without hitting Europe origin
c.Set("Cache-Control", "public, max-age=86400, s-maxage=2592000, immutable")
case SemiDynamic:
// Instant edge delivery; background revalidation over transatlantic backbone
c.Set("Cache-Control", "public, max-age=60, s-maxage=86400, stale-while-revalidate=600")
case Realtime:
c.Set("Cache-Control", "public, max-age=5, s-maxage=10, stale-while-revalidate=30")
case NoCache:
c.Set("Cache-Control", "no-store, no-cache, must-revalidate")
return c.Next()
}
// Cloudflare Cache-Tag header for instantaneous programmatic purge by entity
if len(tags) > 0 {
var tagHeader string
for i, tag := range tags {
if i > 0 {
tagHeader += ","
}
tagHeader += tag
}
c.Set("Cache-Tag", tagHeader)
}
c.Set("Vary", "Accept-Encoding, Origin")
return c.Next()
}
}
// ComputeETag generates a weak ETag based on response body bytes for 304 Not Modified optimization
func ComputeETag(data []byte) string {
hash := sha256.Sum256(data)
return fmt.Sprintf(`W/"%s"`, hex.EncodeToString(hash[:8]))
}