diff --git a/Dockerfile b/Dockerfile index 9f0af2e..0700431 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,8 @@ FROM golang:1.24-bullseye AS builder ENV CGO_ENABLED=1 ENV GOOS=linux # Changed from Apple Silicon arm64 config. -ENV GOARCH=amd64 -# ENV GOARCH=arm64 +# ENV GOARCH=amd64 +ENV GOARCH=arm64 WORKDIR /app diff --git a/controllers/game_controller.go b/controllers/game_controller.go new file mode 100644 index 0000000..bc78245 --- /dev/null +++ b/controllers/game_controller.go @@ -0,0 +1,349 @@ +package controllers + +import ( + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + "github.com/nprasad2077/NBA_Go/models" + "github.com/nprasad2077/NBA_Go/utils" +) + +// DTOs (Data Transfer Objects) for API responses +// These structs define the exact JSON output, ensuring team names are abbreviated. + +type LineScoreDTO struct { + Team string `json:"team"` // Abbreviation + Q1 int `json:"q1"` + Q2 int `json:"q2"` + Q3 int `json:"q3"` + Q4 int `json:"q4"` + OT1 int `json:"ot1"` + OT2 int `json:"ot2"` + OT3 int `json:"ot3"` + Total int `json:"total"` +} + +type PlayerGameBasicStatDTO struct { + PlayerID string `json:"playerId"` + PlayerName string `json:"playerName"` + Team string `json:"team"` // Abbreviation + Status string `json:"status"` + MP string `json:"mp"` + FG int `json:"fg"` + FGA int `json:"fga"` + FGPercent float64 `json:"fgPercent"` + ThreeP int `json:"threeP"` + ThreePA int `json:"threePa"` + ThreePPercent float64 `json:"threePPercent"` + FT int `json:"ft"` + FTA int `json:"fta"` + FTPercent float64 `json:"ftPercent"` + ORB int `json:"orb"` + DRB int `json:"drb"` + TRB int `json:"trb"` + AST int `json:"ast"` + STL int `json:"stl"` + BLK int `json:"blk"` + TOV int `json:"tov"` + PF int `json:"pf"` + PTS int `json:"pts"` + GmSc float64 `json:"gmSc"` + PlusMinus int `json:"plusMinus"` +} + +type PlayerGameAdvStatDTO struct { + PlayerID string `json:"playerId"` + PlayerName string `json:"playerName"` + Team string `json:"team"` // Abbreviation + MP string `json:"mp"` + TSPercent float64 `json:"tsPercent"` + EFGPercent float64 `json:"efgPercent"` + ThreePAr float64 `json:"threePAr"` + FTr float64 `json:"fTr"` + ORBPercent float64 `json:"orbPercent"` + DRBPercent float64 `json:"drbPercent"` + TRBPercent float64 `json:"trbPercent"` + ASTPercent float64 `json:"astPercent"` + STLPercent float64 `json:"stlPercent"` + BLKPercent float64 `json:"blkPercent"` + TOVPercent float64 `json:"tovPercent"` + USGPercent float64 `json:"usgPercent"` + ORtg int `json:"oRtg"` + DRtg int `json:"dRtg"` + BPM float64 `json:"bpm"` +} + +type TeamGameBasicStatDTO struct { + Team string `json:"team"` // Abbreviation + FG int `json:"fg"` + FGA int `json:"fga"` + FGPercent float64 `json:"fgPercent"` + ThreeP int `json:"threeP"` + ThreePA int `json:"threePa"` + ThreePPercent float64 `json:"threePPercent"` + FT int `json:"ft"` + FTA int `json:"fta"` + FTPercent float64 `json:"ftPercent"` + ORB int `json:"orb"` + DRB int `json:"drb"` + TRB int `json:"trb"` + AST int `json:"ast"` + STL int `json:"stl"` + BLK int `json:"blk"` + TOV int `json:"tov"` + PF int `json:"pf"` + PTS int `json:"pts"` +} + +type TeamGameAdvStatDTO struct { + Team string `json:"team"` // Abbreviation + ORtg float64 `json:"oRtg"` + DRtg float64 `json:"dRtg"` + TSPercent float64 `json:"tsPercent"` + EFGPercent float64 `json:"efgPercent"` + ThreePAr float64 `json:"threePAr"` + FTr float64 `json:"fTr"` + ORBPercent float64 `json:"orbPercent"` + DRBPercent float64 `json:"drbPercent"` + TRBPercent float64 `json:"trbPercent"` + ASTPercent float64 `json:"astPercent"` + STLPercent float64 `json:"stlPercent"` + BLKPercent float64 `json:"blkPercent"` + TOVPercent float64 `json:"tovPercent"` +} + +// GameResponseDTO is the top-level object for a single game in the response. +type GameResponseDTO struct { + ID uint `json:"id"` + GameID string `json:"gameId"` + Date time.Time `json:"date"` + IsPlayoff bool `json:"isPlayoff"` + StartTimeET string `json:"startTimeET"` + Arena string `json:"arena"` + VisitorTeam string `json:"visitorTeam"` + VisitorPTS int `json:"visitorPts"` + HomeTeam string `json:"homeTeam"` + HomePTS int `json:"homePts"` + GameDuration string `json:"gameDuration"` + BoxScoreURL string `json:"boxScoreUrl"` + LineScores []LineScoreDTO `json:"lineScores,omitempty"` + PlayerGameBasicStats []PlayerGameBasicStatDTO `json:"playerGameBasicStats,omitempty"` + PlayerGameAdvStats []PlayerGameAdvStatDTO `json:"playerGameAdvStats,omitempty"` + TeamGameBasicStats []TeamGameBasicStatDTO `json:"teamGameBasicStats,omitempty"` + TeamGameAdvStats []TeamGameAdvStatDTO `json:"teamGameAdvStats,omitempty"` +} + +// GamesResponse is the final paginated structure returned by the API. +type GamesResponse struct { + Data []GameResponseDTO `json:"data"` + Pagination struct { + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + Pages int64 `json:"pages"` + } `json:"pagination"` +} + +// gameSortMap translates API sort parameters to database column names. +var gameSortMap = map[string]string{ + "date": "date", + "visitorPts": "visitor_pts", + "homePts": "home_pts", + "gameDuration": "game_duration", +} + +// GetGames godoc +// @Summary Get game data +// @Description Returns a paginated list of games with optional filters and included associations. +// @Tags Games +// @Produce json +// @Param date query string false "Filter by date (YYYY-MM-DD)" +// @Param team query string false "Filter by team abbreviation (e.g., LAL)" +// @Param gameId query string false "Filter by a specific Game ID" +// @Param isPlayoff query bool false "Filter for playoff games" +// @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., date)" default(date) +// @Param ascending query bool false "Sort ascending" default(false) +// @Param include query string false "Comma-separated list of associations to include (e.g., lineScores,playerGameBasicStats,teamGameAdvStats)" +// @Success 200 {object} controllers.GamesResponse +// @Failure 400 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /api/games [get] +func GetGames(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + // --- Build Query --- + query := db.Model(&models.Game{}) + + // --- Filtering --- + if gameId := c.Query("gameId"); gameId != "" { + query = query.Where("game_id = ?", gameId) + } + if dateStr := c.Query("date"); dateStr != "" { + parsedDate, err := time.Parse("2006-01-02", dateStr) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid date format. Use YYYY-MM-DD."}) + } + query = query.Where("date = ?", parsedDate) + } + if teamAbbr := c.Query("team"); teamAbbr != "" { + // Use the helper function to get the full name for the DB query + fullName := utils.GetFullName(strings.ToUpper(teamAbbr)) + if fullName != teamAbbr { // Check if a match was found + query = query.Where("visitor_team = ? OR home_team = ?", fullName, fullName) + } + } + if c.Query("isPlayoff") != "" { + query = query.Where("is_playoff = ?", c.QueryBool("isPlayoff")) + } + + // --- Preloading Associations --- + includeParam := c.Query("include") + if includeParam != "" { + associations := strings.Split(includeParam, ",") + for _, assoc := range associations { + // Preload the original model association based on the JSON field name + // GORM is smart enough to handle the mapping from struct field to relation + switch strings.TrimSpace(assoc) { + case "lineScores": + query = query.Preload("LineScores") + case "playerGameBasicStats": + query = query.Preload("PlayerGameBasicStats") + case "playerGameAdvStats": + query = query.Preload("PlayerGameAdvStats") + case "teamGameBasicStats": + query = query.Preload("TeamGameBasicStats") + case "teamGameAdvStats": + query = query.Preload("TeamGameAdvStats") + } + } + } + + // --- Sorting --- + sortByParam := c.Query("sortBy", "date") + sortBy, ok := gameSortMap[sortByParam] + if !ok { + sortBy = "date" + } + order := sortBy + " DESC" + if c.QueryBool("ascending", false) { + order = sortBy + " ASC" + } + query = query.Order(order) + + // --- Pagination & Data Fetching --- + var total int64 + query.Count(&total) + + page := c.QueryInt("page", 1) + pageSize := c.QueryInt("pageSize", 20) + offset := (page - 1) * pageSize + + var games []models.Game + if err := query.Limit(pageSize).Offset(offset).Find(&games).Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + + // --- Transformation from Model to DTO --- + gameDTOs := make([]GameResponseDTO, len(games)) + for i, game := range games { + gameDTOs[i] = toGameResponseDTO(game) + } + + // --- Build Final Response --- + resp := GamesResponse{Data: gameDTOs} + resp.Pagination.Total = total + resp.Pagination.Page = page + resp.Pagination.PageSize = pageSize + resp.Pagination.Pages = (total + int64(pageSize) - 1) / int64(pageSize) + + return c.JSON(resp) + } +} + +// toGameResponseDTO converts a Game model to its corresponding DTO, including nested associations. +func toGameResponseDTO(game models.Game) GameResponseDTO { + dto := GameResponseDTO{ + ID: game.ID, + GameID: game.GameID, + Date: game.Date, + IsPlayoff: game.IsPlayoff, + StartTimeET: game.StartTimeET, + Arena: game.Arena, + VisitorTeam: utils.GetAbbreviation(game.VisitorTeam), + VisitorPTS: game.VisitorPTS, + HomeTeam: utils.GetAbbreviation(game.HomeTeam), + HomePTS: game.HomePTS, + GameDuration: game.GameDuration, + BoxScoreURL: game.BoxScoreURL, + } + + // Transform nested slices if they were loaded + if len(game.LineScores) > 0 { + dto.LineScores = make([]LineScoreDTO, len(game.LineScores)) + for i, ls := range game.LineScores { + dto.LineScores[i] = LineScoreDTO{ + Team: utils.GetAbbreviation(ls.Team), + Q1: ls.Q1, Q2: ls.Q2, Q3: ls.Q3, Q4: ls.Q4, + OT1: ls.OT1, OT2: ls.OT2, OT3: ls.OT3, + Total: ls.Total, + } + } + } + + if len(game.PlayerGameBasicStats) > 0 { + dto.PlayerGameBasicStats = make([]PlayerGameBasicStatDTO, len(game.PlayerGameBasicStats)) + for i, pbs := range game.PlayerGameBasicStats { + dto.PlayerGameBasicStats[i] = PlayerGameBasicStatDTO{ + PlayerID: pbs.PlayerID, PlayerName: pbs.PlayerName, Team: utils.GetAbbreviation(pbs.Team), + Status: pbs.Status, MP: pbs.MP, FG: pbs.FG, FGA: pbs.FGA, FGPercent: pbs.FGPercent, + ThreeP: pbs.ThreeP, ThreePA: pbs.ThreePA, ThreePPercent: pbs.ThreePPercent, + FT: pbs.FT, FTA: pbs.FTA, FTPercent: pbs.FTPercent, ORB: pbs.ORB, DRB: pbs.DRB, + TRB: pbs.TRB, AST: pbs.AST, STL: pbs.STL, BLK: pbs.BLK, TOV: pbs.TOV, PF: pbs.PF, + PTS: pbs.PTS, GmSc: pbs.GmSc, PlusMinus: pbs.PlusMinus, + } + } + } + + if len(game.PlayerGameAdvStats) > 0 { + dto.PlayerGameAdvStats = make([]PlayerGameAdvStatDTO, len(game.PlayerGameAdvStats)) + for i, pas := range game.PlayerGameAdvStats { + dto.PlayerGameAdvStats[i] = PlayerGameAdvStatDTO{ + PlayerID: pas.PlayerID, PlayerName: pas.PlayerName, Team: utils.GetAbbreviation(pas.Team), + MP: pas.MP, TSPercent: pas.TSPercent, EFGPercent: pas.EFGPercent, ThreePAr: pas.ThreePAr, + FTr: pas.FTr, ORBPercent: pas.ORBPercent, DRBPercent: pas.DRBPercent, TRBPercent: pas.TRBPercent, + ASTPercent: pas.ASTPercent, STLPercent: pas.STLPercent, BLKPercent: pas.BLKPercent, + TOVPercent: pas.TOVPercent, USGPercent: pas.USGPercent, ORtg: pas.ORtg, DRtg: pas.DRtg, BPM: pas.BPM, + } + } + } + + if len(game.TeamGameBasicStats) > 0 { + dto.TeamGameBasicStats = make([]TeamGameBasicStatDTO, len(game.TeamGameBasicStats)) + for i, tbs := range game.TeamGameBasicStats { + dto.TeamGameBasicStats[i] = TeamGameBasicStatDTO{ + Team: utils.GetAbbreviation(tbs.Team), FG: tbs.FG, FGA: tbs.FGA, FGPercent: tbs.FGPercent, + ThreeP: tbs.ThreeP, ThreePA: tbs.ThreePA, ThreePPercent: tbs.ThreePPercent, + FT: tbs.FT, FTA: tbs.FTA, FTPercent: tbs.FTPercent, ORB: tbs.ORB, DRB: tbs.DRB, + TRB: tbs.TRB, AST: tbs.AST, STL: tbs.STL, BLK: tbs.BLK, TOV: tbs.TOV, PF: tbs.PF, PTS: tbs.PTS, + } + } + } + + if len(game.TeamGameAdvStats) > 0 { + dto.TeamGameAdvStats = make([]TeamGameAdvStatDTO, len(game.TeamGameAdvStats)) + for i, tas := range game.TeamGameAdvStats { + dto.TeamGameAdvStats[i] = TeamGameAdvStatDTO{ + Team: utils.GetAbbreviation(tas.Team), ORtg: tas.ORtg, DRtg: tas.DRtg, TSPercent: tas.TSPercent, + EFGPercent: tas.EFGPercent, ThreePAr: tas.ThreePAr, FTr: tas.FTr, ORBPercent: tas.ORBPercent, + DRBPercent: tas.DRBPercent, TRBPercent: tas.TRBPercent, ASTPercent: tas.ASTPercent, + STLPercent: tas.STLPercent, BLKPercent: tas.BLKPercent, TOVPercent: tas.TOVPercent, + } + } + } + + return dto +} \ No newline at end of file diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 847d123..0968822 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -25,7 +25,7 @@ services: db-init: build: . env_file: .env - command: ["/nba_go", "import-data"] + command: ["/nba_go"] depends_on: postgres: condition: service_healthy diff --git a/docs/docs.go b/docs/docs.go index 22f31b5..0e4aad6 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -15,6 +15,104 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/api/games": { + "get": { + "description": "Returns a paginated list of games with optional filters and included associations.", + "produces": [ + "application/json" + ], + "tags": [ + "Games" + ], + "summary": "Get game data", + "parameters": [ + { + "type": "string", + "description": "Filter by date (YYYY-MM-DD)", + "name": "date", + "in": "query" + }, + { + "type": "string", + "description": "Filter by team abbreviation (e.g., LAL)", + "name": "team", + "in": "query" + }, + { + "type": "string", + "description": "Filter by a specific Game ID", + "name": "gameId", + "in": "query" + }, + { + "type": "boolean", + "description": "Filter for playoff games", + "name": "isPlayoff", + "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", + "default": "date", + "description": "Field to sort by (e.g., date)", + "name": "sortBy", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "description": "Sort ascending", + "name": "ascending", + "in": "query" + }, + { + "type": "string", + "description": "Comma-separated list of associations to include (e.g., lineScores,playerGameBasicStats,teamGameAdvStats)", + "name": "include", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.GamesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/playeradvancedstats": { "get": { "description": "Returns filtered and paginated player advanced stats", @@ -304,6 +402,393 @@ const docTemplate = `{ } } }, + "controllers.GameResponseDTO": { + "type": "object", + "properties": { + "arena": { + "type": "string" + }, + "boxScoreUrl": { + "type": "string" + }, + "date": { + "type": "string" + }, + "gameDuration": { + "type": "string" + }, + "gameId": { + "type": "string" + }, + "homePts": { + "type": "integer" + }, + "homeTeam": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPlayoff": { + "type": "boolean" + }, + "lineScores": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.LineScoreDTO" + } + }, + "playerGameAdvStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.PlayerGameAdvStatDTO" + } + }, + "playerGameBasicStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.PlayerGameBasicStatDTO" + } + }, + "startTimeET": { + "type": "string" + }, + "teamGameAdvStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.TeamGameAdvStatDTO" + } + }, + "teamGameBasicStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.TeamGameBasicStatDTO" + } + }, + "visitorPts": { + "type": "integer" + }, + "visitorTeam": { + "type": "string" + } + } + }, + "controllers.GamesResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.GameResponseDTO" + } + }, + "pagination": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "pages": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + } + } + }, + "controllers.LineScoreDTO": { + "type": "object", + "properties": { + "ot1": { + "type": "integer" + }, + "ot2": { + "type": "integer" + }, + "ot3": { + "type": "integer" + }, + "q1": { + "type": "integer" + }, + "q2": { + "type": "integer" + }, + "q3": { + "type": "integer" + }, + "q4": { + "type": "integer" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "total": { + "type": "integer" + } + } + }, + "controllers.PlayerGameAdvStatDTO": { + "type": "object", + "properties": { + "astPercent": { + "type": "number" + }, + "blkPercent": { + "type": "number" + }, + "bpm": { + "type": "number" + }, + "dRtg": { + "type": "integer" + }, + "drbPercent": { + "type": "number" + }, + "efgPercent": { + "type": "number" + }, + "fTr": { + "type": "number" + }, + "mp": { + "type": "string" + }, + "oRtg": { + "type": "integer" + }, + "orbPercent": { + "type": "number" + }, + "playerId": { + "type": "string" + }, + "playerName": { + "type": "string" + }, + "stlPercent": { + "type": "number" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threePAr": { + "type": "number" + }, + "tovPercent": { + "type": "number" + }, + "trbPercent": { + "type": "number" + }, + "tsPercent": { + "type": "number" + }, + "usgPercent": { + "type": "number" + } + } + }, + "controllers.PlayerGameBasicStatDTO": { + "type": "object", + "properties": { + "ast": { + "type": "integer" + }, + "blk": { + "type": "integer" + }, + "drb": { + "type": "integer" + }, + "fg": { + "type": "integer" + }, + "fgPercent": { + "type": "number" + }, + "fga": { + "type": "integer" + }, + "ft": { + "type": "integer" + }, + "ftPercent": { + "type": "number" + }, + "fta": { + "type": "integer" + }, + "gmSc": { + "type": "number" + }, + "mp": { + "type": "string" + }, + "orb": { + "type": "integer" + }, + "pf": { + "type": "integer" + }, + "playerId": { + "type": "string" + }, + "playerName": { + "type": "string" + }, + "plusMinus": { + "type": "integer" + }, + "pts": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "stl": { + "type": "integer" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threeP": { + "type": "integer" + }, + "threePPercent": { + "type": "number" + }, + "threePa": { + "type": "integer" + }, + "tov": { + "type": "integer" + }, + "trb": { + "type": "integer" + } + } + }, + "controllers.TeamGameAdvStatDTO": { + "type": "object", + "properties": { + "astPercent": { + "type": "number" + }, + "blkPercent": { + "type": "number" + }, + "dRtg": { + "type": "number" + }, + "drbPercent": { + "type": "number" + }, + "efgPercent": { + "type": "number" + }, + "fTr": { + "type": "number" + }, + "oRtg": { + "type": "number" + }, + "orbPercent": { + "type": "number" + }, + "stlPercent": { + "type": "number" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threePAr": { + "type": "number" + }, + "tovPercent": { + "type": "number" + }, + "trbPercent": { + "type": "number" + }, + "tsPercent": { + "type": "number" + } + } + }, + "controllers.TeamGameBasicStatDTO": { + "type": "object", + "properties": { + "ast": { + "type": "integer" + }, + "blk": { + "type": "integer" + }, + "drb": { + "type": "integer" + }, + "fg": { + "type": "integer" + }, + "fgPercent": { + "type": "number" + }, + "fga": { + "type": "integer" + }, + "ft": { + "type": "integer" + }, + "ftPercent": { + "type": "number" + }, + "fta": { + "type": "integer" + }, + "orb": { + "type": "integer" + }, + "pf": { + "type": "integer" + }, + "pts": { + "type": "integer" + }, + "stl": { + "type": "integer" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threeP": { + "type": "integer" + }, + "threePPercent": { + "type": "number" + }, + "threePa": { + "type": "integer" + }, + "tov": { + "type": "integer" + }, + "trb": { + "type": "integer" + } + } + }, "models.PlayerAdvancedStat": { "type": "object", "properties": { @@ -460,6 +945,9 @@ const docTemplate = `{ } }, "tags": [ + { + "name": "Games" + }, { "name": "PlayerTotals" }, diff --git a/docs/swagger.json b/docs/swagger.json index 7555ca2..2c2e10d 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -11,6 +11,104 @@ }, "basePath": "/", "paths": { + "/api/games": { + "get": { + "description": "Returns a paginated list of games with optional filters and included associations.", + "produces": [ + "application/json" + ], + "tags": [ + "Games" + ], + "summary": "Get game data", + "parameters": [ + { + "type": "string", + "description": "Filter by date (YYYY-MM-DD)", + "name": "date", + "in": "query" + }, + { + "type": "string", + "description": "Filter by team abbreviation (e.g., LAL)", + "name": "team", + "in": "query" + }, + { + "type": "string", + "description": "Filter by a specific Game ID", + "name": "gameId", + "in": "query" + }, + { + "type": "boolean", + "description": "Filter for playoff games", + "name": "isPlayoff", + "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", + "default": "date", + "description": "Field to sort by (e.g., date)", + "name": "sortBy", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "description": "Sort ascending", + "name": "ascending", + "in": "query" + }, + { + "type": "string", + "description": "Comma-separated list of associations to include (e.g., lineScores,playerGameBasicStats,teamGameAdvStats)", + "name": "include", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.GamesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/playeradvancedstats": { "get": { "description": "Returns filtered and paginated player advanced stats", @@ -300,6 +398,393 @@ } } }, + "controllers.GameResponseDTO": { + "type": "object", + "properties": { + "arena": { + "type": "string" + }, + "boxScoreUrl": { + "type": "string" + }, + "date": { + "type": "string" + }, + "gameDuration": { + "type": "string" + }, + "gameId": { + "type": "string" + }, + "homePts": { + "type": "integer" + }, + "homeTeam": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPlayoff": { + "type": "boolean" + }, + "lineScores": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.LineScoreDTO" + } + }, + "playerGameAdvStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.PlayerGameAdvStatDTO" + } + }, + "playerGameBasicStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.PlayerGameBasicStatDTO" + } + }, + "startTimeET": { + "type": "string" + }, + "teamGameAdvStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.TeamGameAdvStatDTO" + } + }, + "teamGameBasicStats": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.TeamGameBasicStatDTO" + } + }, + "visitorPts": { + "type": "integer" + }, + "visitorTeam": { + "type": "string" + } + } + }, + "controllers.GamesResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.GameResponseDTO" + } + }, + "pagination": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "pages": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + } + } + }, + "controllers.LineScoreDTO": { + "type": "object", + "properties": { + "ot1": { + "type": "integer" + }, + "ot2": { + "type": "integer" + }, + "ot3": { + "type": "integer" + }, + "q1": { + "type": "integer" + }, + "q2": { + "type": "integer" + }, + "q3": { + "type": "integer" + }, + "q4": { + "type": "integer" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "total": { + "type": "integer" + } + } + }, + "controllers.PlayerGameAdvStatDTO": { + "type": "object", + "properties": { + "astPercent": { + "type": "number" + }, + "blkPercent": { + "type": "number" + }, + "bpm": { + "type": "number" + }, + "dRtg": { + "type": "integer" + }, + "drbPercent": { + "type": "number" + }, + "efgPercent": { + "type": "number" + }, + "fTr": { + "type": "number" + }, + "mp": { + "type": "string" + }, + "oRtg": { + "type": "integer" + }, + "orbPercent": { + "type": "number" + }, + "playerId": { + "type": "string" + }, + "playerName": { + "type": "string" + }, + "stlPercent": { + "type": "number" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threePAr": { + "type": "number" + }, + "tovPercent": { + "type": "number" + }, + "trbPercent": { + "type": "number" + }, + "tsPercent": { + "type": "number" + }, + "usgPercent": { + "type": "number" + } + } + }, + "controllers.PlayerGameBasicStatDTO": { + "type": "object", + "properties": { + "ast": { + "type": "integer" + }, + "blk": { + "type": "integer" + }, + "drb": { + "type": "integer" + }, + "fg": { + "type": "integer" + }, + "fgPercent": { + "type": "number" + }, + "fga": { + "type": "integer" + }, + "ft": { + "type": "integer" + }, + "ftPercent": { + "type": "number" + }, + "fta": { + "type": "integer" + }, + "gmSc": { + "type": "number" + }, + "mp": { + "type": "string" + }, + "orb": { + "type": "integer" + }, + "pf": { + "type": "integer" + }, + "playerId": { + "type": "string" + }, + "playerName": { + "type": "string" + }, + "plusMinus": { + "type": "integer" + }, + "pts": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "stl": { + "type": "integer" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threeP": { + "type": "integer" + }, + "threePPercent": { + "type": "number" + }, + "threePa": { + "type": "integer" + }, + "tov": { + "type": "integer" + }, + "trb": { + "type": "integer" + } + } + }, + "controllers.TeamGameAdvStatDTO": { + "type": "object", + "properties": { + "astPercent": { + "type": "number" + }, + "blkPercent": { + "type": "number" + }, + "dRtg": { + "type": "number" + }, + "drbPercent": { + "type": "number" + }, + "efgPercent": { + "type": "number" + }, + "fTr": { + "type": "number" + }, + "oRtg": { + "type": "number" + }, + "orbPercent": { + "type": "number" + }, + "stlPercent": { + "type": "number" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threePAr": { + "type": "number" + }, + "tovPercent": { + "type": "number" + }, + "trbPercent": { + "type": "number" + }, + "tsPercent": { + "type": "number" + } + } + }, + "controllers.TeamGameBasicStatDTO": { + "type": "object", + "properties": { + "ast": { + "type": "integer" + }, + "blk": { + "type": "integer" + }, + "drb": { + "type": "integer" + }, + "fg": { + "type": "integer" + }, + "fgPercent": { + "type": "number" + }, + "fga": { + "type": "integer" + }, + "ft": { + "type": "integer" + }, + "ftPercent": { + "type": "number" + }, + "fta": { + "type": "integer" + }, + "orb": { + "type": "integer" + }, + "pf": { + "type": "integer" + }, + "pts": { + "type": "integer" + }, + "stl": { + "type": "integer" + }, + "team": { + "description": "Abbreviation", + "type": "string" + }, + "threeP": { + "type": "integer" + }, + "threePPercent": { + "type": "number" + }, + "threePa": { + "type": "integer" + }, + "tov": { + "type": "integer" + }, + "trb": { + "type": "integer" + } + } + }, "models.PlayerAdvancedStat": { "type": "object", "properties": { @@ -456,6 +941,9 @@ } }, "tags": [ + { + "name": "Games" + }, { "name": "PlayerTotals" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 21705c3..ef93c65 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -18,6 +18,263 @@ definitions: type: integer type: object type: object + controllers.GameResponseDTO: + properties: + arena: + type: string + boxScoreUrl: + type: string + date: + type: string + gameDuration: + type: string + gameId: + type: string + homePts: + type: integer + homeTeam: + type: string + id: + type: integer + isPlayoff: + type: boolean + lineScores: + items: + $ref: '#/definitions/controllers.LineScoreDTO' + type: array + playerGameAdvStats: + items: + $ref: '#/definitions/controllers.PlayerGameAdvStatDTO' + type: array + playerGameBasicStats: + items: + $ref: '#/definitions/controllers.PlayerGameBasicStatDTO' + type: array + startTimeET: + type: string + teamGameAdvStats: + items: + $ref: '#/definitions/controllers.TeamGameAdvStatDTO' + type: array + teamGameBasicStats: + items: + $ref: '#/definitions/controllers.TeamGameBasicStatDTO' + type: array + visitorPts: + type: integer + visitorTeam: + type: string + type: object + controllers.GamesResponse: + properties: + data: + items: + $ref: '#/definitions/controllers.GameResponseDTO' + type: array + pagination: + properties: + page: + type: integer + pageSize: + type: integer + pages: + type: integer + total: + type: integer + type: object + type: object + controllers.LineScoreDTO: + properties: + ot1: + type: integer + ot2: + type: integer + ot3: + type: integer + q1: + type: integer + q2: + type: integer + q3: + type: integer + q4: + type: integer + team: + description: Abbreviation + type: string + total: + type: integer + type: object + controllers.PlayerGameAdvStatDTO: + properties: + astPercent: + type: number + blkPercent: + type: number + bpm: + type: number + dRtg: + type: integer + drbPercent: + type: number + efgPercent: + type: number + fTr: + type: number + mp: + type: string + oRtg: + type: integer + orbPercent: + type: number + playerId: + type: string + playerName: + type: string + stlPercent: + type: number + team: + description: Abbreviation + type: string + threePAr: + type: number + tovPercent: + type: number + trbPercent: + type: number + tsPercent: + type: number + usgPercent: + type: number + type: object + controllers.PlayerGameBasicStatDTO: + properties: + ast: + type: integer + blk: + type: integer + drb: + type: integer + fg: + type: integer + fgPercent: + type: number + fga: + type: integer + ft: + type: integer + ftPercent: + type: number + fta: + type: integer + gmSc: + type: number + mp: + type: string + orb: + type: integer + pf: + type: integer + playerId: + type: string + playerName: + type: string + plusMinus: + type: integer + pts: + type: integer + status: + type: string + stl: + type: integer + team: + description: Abbreviation + type: string + threeP: + type: integer + threePPercent: + type: number + threePa: + type: integer + tov: + type: integer + trb: + type: integer + type: object + controllers.TeamGameAdvStatDTO: + properties: + astPercent: + type: number + blkPercent: + type: number + dRtg: + type: number + drbPercent: + type: number + efgPercent: + type: number + fTr: + type: number + oRtg: + type: number + orbPercent: + type: number + stlPercent: + type: number + team: + description: Abbreviation + type: string + threePAr: + type: number + tovPercent: + type: number + trbPercent: + type: number + tsPercent: + type: number + type: object + controllers.TeamGameBasicStatDTO: + properties: + ast: + type: integer + blk: + type: integer + drb: + type: integer + fg: + type: integer + fgPercent: + type: number + fga: + type: integer + ft: + type: integer + ftPercent: + type: number + fta: + type: integer + orb: + type: integer + pf: + type: integer + pts: + type: integer + stl: + type: integer + team: + description: Abbreviation + type: string + threeP: + type: integer + threePPercent: + type: number + threePa: + type: integer + tov: + type: integer + trb: + type: integer + type: object models.PlayerAdvancedStat: properties: age: @@ -127,6 +384,73 @@ info: title: NBA_Go API version: "1.0" paths: + /api/games: + get: + description: Returns a paginated list of games with optional filters and included + associations. + parameters: + - description: Filter by date (YYYY-MM-DD) + in: query + name: date + type: string + - description: Filter by team abbreviation (e.g., LAL) + in: query + name: team + type: string + - description: Filter by a specific Game ID + in: query + name: gameId + type: string + - description: Filter for playoff games + in: query + name: isPlayoff + type: boolean + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 20 + description: Page size + in: query + name: pageSize + type: integer + - default: date + description: Field to sort by (e.g., date) + in: query + name: sortBy + type: string + - default: false + description: Sort ascending + in: query + name: ascending + type: boolean + - description: Comma-separated list of associations to include (e.g., lineScores,playerGameBasicStats,teamGameAdvStats) + in: query + name: include + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.GamesResponse' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Get game data + tags: + - Games /api/playeradvancedstats: get: consumes: @@ -306,6 +630,7 @@ schemes: - http swagger: "2.0" tags: +- name: Games - name: PlayerTotals - name: PlayerStats - name: PlayerShotChart diff --git a/main.go b/main.go index af4b231..c039695 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ // @schemes http // @BasePath / // +// @tag.name Games // @tag.name PlayerTotals // @tag.name PlayerStats // @tag.name PlayerShotChart @@ -114,6 +115,7 @@ func main() { routes.RegisterPlayerAdvancedRoutes(app, db) routes.RegisterPlayerTotalRoutes(app, db) routes.RegisterPlayerShotChartRoutes(app, db) + routes.RegisterGameRoutes(app, db) /* ---------- START & SHUTDOWN ---------- */ go func() { diff --git a/routes/game_routes.go b/routes/game_routes.go new file mode 100644 index 0000000..44eef4b --- /dev/null +++ b/routes/game_routes.go @@ -0,0 +1,15 @@ +package routes + +import ( + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + "github.com/nprasad2077/NBA_Go/controllers" +) + +func RegisterGameRoutes(app *fiber.App, db *gorm.DB) { + // Create a new group for game-related endpoints + api := app.Group("/api/games") + + // Register the GET endpoint to fetch games + api.Get("/", controllers.GetGames(db)) +} \ No newline at end of file diff --git a/utils/teams.go b/utils/teams.go new file mode 100644 index 0000000..4723b6e --- /dev/null +++ b/utils/teams.go @@ -0,0 +1,64 @@ +// utils/teams.go +package utils + +// teamNameToAbbr is un-exported (private to the package) and serves as the single source of truth. +var teamNameToAbbr = map[string]string{ + "Atlanta Hawks": "ATL", + "Boston Celtics": "BOS", + "Brooklyn Nets": "BKN", + "Charlotte Hornets": "CHA", + "Chicago Bulls": "CHI", + "Cleveland Cavaliers": "CLE", + "Dallas Mavericks": "DAL", + "Denver Nuggets": "DEN", + "Detroit Pistons": "DET", + "Golden State Warriors": "GSW", + "Houston Rockets": "HOU", + "Indiana Pacers": "IND", + "Los Angeles Clippers": "LAC", + "Los Angeles Lakers": "LAL", + "Memphis Grizzlies": "MEM", + "Miami Heat": "MIA", + "Milwaukee Bucks": "MIL", + "Minnesota Timberwolves":"MIN", + "New Orleans Pelicans": "NOP", + "New York Knicks": "NYK", + "Oklahoma City Thunder": "OKC", + "Orlando Magic": "ORL", + "Philadelphia 76ers": "PHI", + "Phoenix Suns": "PHX", + "Portland Trail Blazers":"POR", + "Sacramento Kings": "SAC", + "San Antonio Spurs": "SAS", + "Toronto Raptors": "TOR", + "Utah Jazz": "UTA", + "Washington Wizards": "WAS", +} + +// TeamAbbrToName is the exported reverse map. +var TeamAbbrToName map[string]string + +// init() runs automatically when the package is first used. +// It creates the reverse map so it's ready for use. +func init() { + TeamAbbrToName = make(map[string]string, len(teamNameToAbbr)) + for name, abbr := range teamNameToAbbr { + TeamAbbrToName[abbr] = name + } +} + +// GetAbbreviation looks up a team's full name and returns its abbreviation. +func GetAbbreviation(fullName string) string { + if abbr, ok := teamNameToAbbr[fullName]; ok { + return abbr + } + return fullName // Fallback +} + +// GetFullName looks up a team's abbreviation and returns its full name. +func GetFullName(abbr string) string { + if name, ok := TeamAbbrToName[abbr]; ok { + return name + } + return abbr // Fallback +} \ No newline at end of file