From 61524f26ee2adb9aae02a24dd93516c4b178d28b Mon Sep 17 00:00:00 2001 From: ChrisRoseProd Date: Thu, 7 May 2026 18:12:46 +0100 Subject: [PATCH 1/6] UDFA Bidding Process Built --- controller/AdminController.go | 52 +++++++------------ controller/UDFAController.go | 64 ++++++++++++++++++++++++ main.go | 10 ++++ managers/UDFAManager.go | 94 +++++++++++++++++++++++++++++++++++ repository/UDFARepository.go | 26 ++++++++++ structs/NFLUDFA.go | 22 ++++++++ 6 files changed, 233 insertions(+), 35 deletions(-) create mode 100644 controller/UDFAController.go create mode 100644 managers/UDFAManager.go create mode 100644 repository/UDFARepository.go create mode 100644 structs/NFLUDFA.go diff --git a/controller/AdminController.go b/controller/AdminController.go index 30c8809..79144a2 100644 --- a/controller/AdminController.go +++ b/controller/AdminController.go @@ -15,9 +15,7 @@ import ( // GetTimeStamp func GetCurrentTimestamp(w http.ResponseWriter, r *http.Request) { - timestamp := managers.GetTimestamp() - json.NewEncoder(w).Encode(timestamp) } @@ -29,23 +27,18 @@ func SyncTimestamp(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } - newTimestamp := managers.UpdateTimestamp(updateTimestampDto) - json.NewEncoder(w).Encode(newTimestamp) } func SyncRecruiting(w http.ResponseWriter, r *http.Request) { ts := managers.GetTimestamp() - managers.SyncRecruiting(ts) - json.NewEncoder(w).Encode("Sync Complete") } func SyncWeek(w http.ResponseWriter, r *http.Request) { newTimestamp := managers.MoveUpWeek() - json.NewEncoder(w).Encode(newTimestamp) } @@ -55,19 +48,32 @@ func SyncTimeslot(w http.ResponseWriter, r *http.Request) { if len(timeslot) == 0 { log.Panicln("Missing timeslot!") } - managers.SyncTimeslot(timeslot) - json.NewEncoder(w).Encode("Timeslot updated") } func SyncFreeAgencyRound(w http.ResponseWriter, r *http.Request) { managers.SyncFreeAgencyOffers() managers.MoveUpInOffseasonFreeAgency() - // managers.AttemptToDecreaseMinimumValues() json.NewEncoder(w).Encode("Moved to next free agency round") } +// NEW FUNCTION: ProcessUDFAs +// Triggered by the Admin Button +func ProcessUDFAs(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + dryRunParam := query.Get("dryRun") + isDryRun := dryRunParam == "true" + + managers.ProcessUDFAs(isDryRun) + + message := "UDFA Processing Complete." + if isDryRun { + message = "UDFA Dry Run Complete. Check server logs for results." + } + json.NewEncoder(w).Encode(message) +} + func SyncMissingRES(w http.ResponseWriter, r *http.Request) { managers.SyncAllMissingEfficiencies() } @@ -76,39 +82,15 @@ func GetWeeksInSeason(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) seasonID := vars["seasonID"] weekID := vars["weekID"] - weeks := managers.GetWeeksInASeason(seasonID, weekID) - json.NewEncoder(w).Encode(weeks) } -// CreateCollegeRecruit? - -// CreateNFLPlayer -- Create NFL Player from template, and then synthetically progress them based on the year of input - -// UpdateTeamRecruitingProfile - -// ApproveCoachForTeam - -// RemoveCoachFromTeam - -// UpdateTeam - -// RunProgressionsForCollege -func RunProgressionsForCollege(w http.ResponseWriter, r *http.Request) { - -} - -// GenerateWalkons func GenerateWalkOns(w http.ResponseWriter, r *http.Request) { managers.GenerateWalkOns() fmt.Println(w, "Walk ons successfully generated.") } -// RunProgressionsForNFL - -// RunProgressionsForJuco? - func SyncTeamRecruitingRanks(w http.ResponseWriter, r *http.Request) { managers.SyncTeamRankings() fmt.Println(w, "Team Ranks successfully generated.") @@ -183,4 +165,4 @@ func SyncToNextSeason(w http.ResponseWriter, r *http.Request) { ts.MoveUpSeason() repository.SaveTimestamp(ts, db) managers.GenerateOffseasonData() -} +} \ No newline at end of file diff --git a/controller/UDFAController.go b/controller/UDFAController.go new file mode 100644 index 0000000..a628492 --- /dev/null +++ b/controller/UDFAController.go @@ -0,0 +1,64 @@ +package controller + +import ( + "encoding/json" + "net/http" + + "github.com/CalebRose/SimFBA/managers" + "github.com/CalebRose/SimFBA/structs" + "github.com/gorilla/mux" +) + +// GetUDFABoardByTeamID - Returns the bidding board for a specific team +func GetUDFABoardByTeamID(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + teamID := vars["teamID"] + + if len(teamID) == 0 { + http.Error(w, "No Team ID provided", http.StatusBadRequest) + return + } + + board := managers.GetUDFABoardByTeamID(teamID) + json.NewEncoder(w).Encode(board) +} + +// AddPlayerToUDFABoard - Adds a player to the team's bidding list +func AddPlayerToUDFABoard(w http.ResponseWriter, r *http.Request) { + var profile structs.NFLUDFAProfile + err := json.NewDecoder(r.Body).Decode(&profile) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + updatedProfile := managers.AddPlayerToUDFABoard(profile) + json.NewEncoder(w).Encode(updatedProfile) +} + +// SaveUDFABoard - Saves the point allocations (1-20) +func SaveUDFABoard(w http.ResponseWriter, r *http.Request) { + var board structs.NFLUDFABoard + err := json.NewDecoder(r.Body).Decode(&board) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + err = managers.SaveUDFABoard(board) + if err != nil { + http.Error(w, "Could not save UDFA board: "+err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(true) +} + +// RemovePlayerFromUDFABoard - Deletes a bid +func RemovePlayerFromUDFABoard(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + profileID := vars["profileID"] + + managers.RemovePlayerFromUDFABoard(profileID) + json.NewEncoder(w).Encode(true) +} \ No newline at end of file diff --git a/main.go b/main.go index 4f65881..5f35e43 100644 --- a/main.go +++ b/main.go @@ -122,6 +122,9 @@ func handleRequests() http.Handler { apiRouter.HandleFunc("/admin/trades/accept/sync/{proposalID}", controller.SyncAcceptedTrade).Methods("GET") apiRouter.HandleFunc("/admin/trades/veto/sync/{proposalID}", controller.VetoAcceptedTrade).Methods("GET") apiRouter.HandleFunc("/admin/trades/cleanup", controller.CleanUpRejectedTrades).Methods("GET") + apiRouter.HandleFunc("/admin/process-udfas", controller.ProcessUDFAs).Methods("GET") + + // Bootstrap apiRouter.HandleFunc("/bootstrap/teams", controller.BootstrapTeamData).Methods("GET") @@ -425,6 +428,13 @@ func handleRequests() http.Handler { apiRouter.HandleFunc("/portal/player/scout/{id}", controller.GetScoutingDataByTransfer).Methods("GET") apiRouter.HandleFunc("/portal/export/players/", controller.ExportPortalPlayersToCSV).Methods("GET") + // UDFA Controls + + apiRouter.HandleFunc("/nfl/udfa/board/{teamID}", controller.GetUDFABoardByTeamID).Methods("GET") + apiRouter.HandleFunc("/nfl/udfa/board/add", controller.AddPlayerToUDFABoard).Methods("POST") + apiRouter.HandleFunc("/nfl/udfa/board/save", controller.SaveUDFABoard).Methods("POST") + apiRouter.HandleFunc("/nfl/udfa/board/remove/{profileID}", controller.RemovePlayerFromUDFABoard).Methods("DELETE") + // Discord Controls apiRouter.HandleFunc("/ds/cfb/team/{teamID}/", controller.GetTeamByTeamIDForDiscord).Methods("GET") apiRouter.HandleFunc("/ds/college/player/indstats/{id}/{week}/", controller.GetCollegePlayerStatsByNameTeamAndWeek).Methods("GET") diff --git a/managers/UDFAManager.go b/managers/UDFAManager.go new file mode 100644 index 0000000..3c9eb39 --- /dev/null +++ b/managers/UDFAManager.go @@ -0,0 +1,94 @@ +package managers + +import ( + "fmt" + "math/rand" + "time" + + "github.com/CalebRose/SimFBA/dbprovider" + "github.com/CalebRose/SimFBA/models" + "github.com/CalebRose/SimFBA/structs" +) + +func ProcessUDFAs(isDryRun bool) { + db := dbprovider.GetInstance().GetDB() + + // 1. Get all Undrafted Players + var undraftedPlayers []models.NFLDraftee + db.Where("draft_pick_id = 0").Find(&undraftedPlayers) + + // 2. Get all Bids + var allBids []structs.NFLUDFAProfile + db.Find(&allBids) + + // Group bids by PlayerID + bidsByPlayer := make(map[uint][]structs.NFLUDFAProfile) + for _, bid := range allBids { + bidsByPlayer[bid.PlayerID] = append(bidsByPlayer[bid.PlayerID], bid) + } + + for _, player := range undraftedPlayers { + bids := bidsByPlayer[player.ID] + if len(bids) == 0 { + continue + } + + // Find winning bid (Highest points) + var winningBid structs.NFLUDFAProfile + maxPoints := 0 + var tiedBids []structs.NFLUDFAProfile + + for _, bid := range bids { + if bid.Points > maxPoints { + maxPoints = bid.Points + winningBid = bid + tiedBids = []structs.NFLUDFAProfile{bid} + } else if bid.Points == maxPoints { + tiedBids = append(tiedBids, bid) + } + } + + // Tie-breaker: Random Roll + if len(tiedBids) > 1 { + rand.Seed(time.Now().UnixNano()) + winningBid = tiedBids[rand.Intn(len(tiedBids))] + } + + if !isDryRun && winningBid.Points > 0 { + SignUDFA(player, winningBid) + } else if isDryRun { + fmt.Printf("DRY RUN: %s %s would sign with %s for %d points\n", player.FirstName, player.LastName, winningBid.TeamAbbr, winningBid.Points) + } + } +} + +func SignUDFA(draftee models.NFLDraftee, bid structs.NFLUDFAProfile) { + db := dbprovider.GetInstance().GetDB() + + // Convert Draftee to NFLPlayer + nflPlayer := structs.NFLPlayer{ + BasePlayer: draftee.BasePlayer, + TeamID: int(bid.TeamID), + TeamAbbr: bid.TeamAbbr, + Experience: 1, + IsActive: true, + } + nflPlayer.ID = draftee.ID + db.Create(&nflPlayer) + + // Create 3-year contract: 0.5 Salary, 0 Bonus + contract := structs.NFLContract{ + NFLPlayerID: int(nflPlayer.ID), + TeamID: bid.TeamID, + Team: bid.TeamAbbr, + ContractLength: 3, + Y1BaseSalary: 0.5, + Y2BaseSalary: 0.5, + Y3BaseSalary: 0.5, + IsActive: true, + } + db.Create(&contract) + + // Delete from draftee table + db.Delete(&draftee) +} \ No newline at end of file diff --git a/repository/UDFARepository.go b/repository/UDFARepository.go new file mode 100644 index 0000000..88c5426 --- /dev/null +++ b/repository/UDFARepository.go @@ -0,0 +1,26 @@ +package repository + +import ( + "github.com/CalebRose/SimFBA/dbprovider" + "github.com/CalebRose/SimFBA/structs" +) + +// GetUDFABoardByTeamID fetches the board and all associated bid profiles +func GetUDFABoardByTeamID(teamID string) structs.NFLUDFABoard { + db := dbprovider.GetInstance().GetDB() + var board structs.NFLUDFABoard + db.Preload("Profiles").Where("team_id = ?", teamID).First(&board) + return board +} + +// SaveUDFAProfile saves or updates a specific bid +func SaveUDFAProfile(profile structs.NFLUDFAProfile) error { + db := dbprovider.GetInstance().GetDB() + return db.Save(&profile).Error +} + +// DeleteUDFAProfile removes a bid from the board +func DeleteUDFAProfile(profileID string) error { + db := dbprovider.GetInstance().GetDB() + return db.Where("id = ?", profileID).Delete(&structs.NFLUDFAProfile{}).Error +} \ No newline at end of file diff --git a/structs/NFLUDFA.go b/structs/NFLUDFA.go new file mode 100644 index 0000000..1452917 --- /dev/null +++ b/structs/NFLUDFA.go @@ -0,0 +1,22 @@ +package structs + +import "github.com/jinzhu/gorm" + +type NFLUDFABoard struct { + gorm.Model + TeamID uint + TeamAbbr string + Profiles []NFLUDFAProfile `gorm:"foreignKey:NFLUDFABoardID"` +} + +type NFLUDFAProfile struct { + gorm.Model + NFLUDFABoardID uint + PlayerID uint + PlayerName string + Position string + TeamID uint + TeamAbbr string + Points int // The 1-20 points allocated + IsSigned bool +} \ No newline at end of file From 427e5049c44b93e2f4702693f69898da2bd085d1 Mon Sep 17 00:00:00 2001 From: ChrisSaterra Date: Thu, 7 May 2026 22:44:53 +0100 Subject: [PATCH 2/6] Feature Complete --- controller/AdminController.go | 18 +---- controller/UDFAController.go | 52 ++++++++++----- main.go | 6 +- managers/ForumManager.go | 67 +++++++++++++++++++ managers/UDFAManager.go | 121 ++++++++++++++++++++++++++++++---- 5 files changed, 217 insertions(+), 47 deletions(-) diff --git a/controller/AdminController.go b/controller/AdminController.go index 79144a2..d0eaaad 100644 --- a/controller/AdminController.go +++ b/controller/AdminController.go @@ -58,22 +58,6 @@ func SyncFreeAgencyRound(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode("Moved to next free agency round") } -// NEW FUNCTION: ProcessUDFAs -// Triggered by the Admin Button -func ProcessUDFAs(w http.ResponseWriter, r *http.Request) { - query := r.URL.Query() - dryRunParam := query.Get("dryRun") - isDryRun := dryRunParam == "true" - - managers.ProcessUDFAs(isDryRun) - - message := "UDFA Processing Complete." - if isDryRun { - message = "UDFA Dry Run Complete. Check server logs for results." - } - json.NewEncoder(w).Encode(message) -} - func SyncMissingRES(w http.ResponseWriter, r *http.Request) { managers.SyncAllMissingEfficiencies() } @@ -165,4 +149,4 @@ func SyncToNextSeason(w http.ResponseWriter, r *http.Request) { ts.MoveUpSeason() repository.SaveTimestamp(ts, db) managers.GenerateOffseasonData() -} \ No newline at end of file +} diff --git a/controller/UDFAController.go b/controller/UDFAController.go index a628492..d9b809e 100644 --- a/controller/UDFAController.go +++ b/controller/UDFAController.go @@ -9,21 +9,19 @@ import ( "github.com/gorilla/mux" ) -// GetUDFABoardByTeamID - Returns the bidding board for a specific team +// GetUDFABoardByTeamID returns the user's specific UDFA bidding board func GetUDFABoardByTeamID(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) teamID := vars["teamID"] - if len(teamID) == 0 { - http.Error(w, "No Team ID provided", http.StatusBadRequest) - return + panic("User did not provide TeamID") } board := managers.GetUDFABoardByTeamID(teamID) json.NewEncoder(w).Encode(board) } -// AddPlayerToUDFABoard - Adds a player to the team's bidding list +// AddPlayerToUDFABoard adds a single player to the user's board func AddPlayerToUDFABoard(w http.ResponseWriter, r *http.Request) { var profile structs.NFLUDFAProfile err := json.NewDecoder(r.Body).Decode(&profile) @@ -32,11 +30,14 @@ func AddPlayerToUDFABoard(w http.ResponseWriter, r *http.Request) { return } - updatedProfile := managers.AddPlayerToUDFABoard(profile) - json.NewEncoder(w).Encode(updatedProfile) + // Call the manager to execute the DB logic + managers.AddPlayerToUDFABoard(profile) + + // Return success to the frontend + json.NewEncoder(w).Encode(true) } -// SaveUDFABoard - Saves the point allocations (1-20) +// SaveUDFABoard updates the points assigned to all players on the board func SaveUDFABoard(w http.ResponseWriter, r *http.Request) { var board structs.NFLUDFABoard err := json.NewDecoder(r.Body).Decode(&board) @@ -45,20 +46,41 @@ func SaveUDFABoard(w http.ResponseWriter, r *http.Request) { return } - err = managers.SaveUDFABoard(board) - if err != nil { - http.Error(w, "Could not save UDFA board: "+err.Error(), http.StatusInternalServerError) - return - } + // Call the manager to execute the DB logic + managers.SaveUDFABoard(board) + // Return success to the frontend json.NewEncoder(w).Encode(true) } -// RemovePlayerFromUDFABoard - Deletes a bid +// RemovePlayerFromUDFABoard deletes a player from the user's board func RemovePlayerFromUDFABoard(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) profileID := vars["profileID"] + if len(profileID) == 0 { + panic("User did not provide profileID") + } + // Call the manager to execute the DB logic managers.RemovePlayerFromUDFABoard(profileID) + + // Return success to the frontend json.NewEncoder(w).Encode(true) -} \ No newline at end of file +} + +// ProcessUDFAs triggers the batch signing process (Used by Admin) +func ProcessUDFAs(w http.ResponseWriter, r *http.Request) { + dryRunParam := r.URL.Query().Get("dryRun") + isDryRun := dryRunParam == "true" + + // Trigger the batch signing logic + managers.ProcessUDFAs(isDryRun) + + // Return a clean success message to display in the Admin Panel + msg := "LIVE UDFA Signings Executed Successfully!" + if isDryRun { + msg = "Dry Run Complete. Check backend server console for simulation results." + } + + json.NewEncoder(w).Encode(msg) +} diff --git a/main.go b/main.go index 5f35e43..eaa162c 100644 --- a/main.go +++ b/main.go @@ -124,8 +124,6 @@ func handleRequests() http.Handler { apiRouter.HandleFunc("/admin/trades/cleanup", controller.CleanUpRejectedTrades).Methods("GET") apiRouter.HandleFunc("/admin/process-udfas", controller.ProcessUDFAs).Methods("GET") - - // Bootstrap apiRouter.HandleFunc("/bootstrap/teams", controller.BootstrapTeamData).Methods("GET") apiRouter.HandleFunc("/bootstrap/landing/{collegeID}/{proID}", controller.BootstrapLandingData).Methods("GET") @@ -428,12 +426,12 @@ func handleRequests() http.Handler { apiRouter.HandleFunc("/portal/player/scout/{id}", controller.GetScoutingDataByTransfer).Methods("GET") apiRouter.HandleFunc("/portal/export/players/", controller.ExportPortalPlayersToCSV).Methods("GET") - // UDFA Controls + // UDFA Controls apiRouter.HandleFunc("/nfl/udfa/board/{teamID}", controller.GetUDFABoardByTeamID).Methods("GET") apiRouter.HandleFunc("/nfl/udfa/board/add", controller.AddPlayerToUDFABoard).Methods("POST") apiRouter.HandleFunc("/nfl/udfa/board/save", controller.SaveUDFABoard).Methods("POST") - apiRouter.HandleFunc("/nfl/udfa/board/remove/{profileID}", controller.RemovePlayerFromUDFABoard).Methods("DELETE") + apiRouter.HandleFunc("/nfl/udfa/board/remove/{profileID}", controller.RemovePlayerFromUDFABoard).Methods("GET") // Discord Controls apiRouter.HandleFunc("/ds/cfb/team/{teamID}/", controller.GetTeamByTeamIDForDiscord).Methods("GET") diff --git a/managers/ForumManager.go b/managers/ForumManager.go index 1744ad3..5107d2d 100644 --- a/managers/ForumManager.go +++ b/managers/ForumManager.go @@ -1051,3 +1051,70 @@ func buildRecruitingSyncParagraphs(season, week int, signings []string) []string return paragraphs } + +// ───────────────────────────────────────────── +// NFL UDFA Sync Thread +// ───────────────────────────────────────────── + +// CreateNFLUDFASyncForumThread creates a system-generated forum thread in +// the "media-simnfl" subforum summarising the signings from the UDFA period. +func CreateNFLUDFASyncForumThread(season int, signings []string) { + ctx := context.Background() + + title := fmt.Sprintf("SimNFL: Season %d UDFA Signings", season) + eventKey := fmt.Sprintf("udfa_sync:nfl:season%d", season) + + paragraphs := buildNFLUDFASyncParagraphs(season, signings) + bodyText := strings.Join(paragraphs, "\n\n") + richBody := buildRichPostBody(paragraphs) + + input := fbsvc.CreateForumThreadInput{ + ForumID: "media-simnfl", + ForumPath: []string{"media", "simnfl"}, + Title: title, + AuthorUID: "system", + AuthorUsername: "SimSN", + AuthorDisplayName: "SimSN System", + CreatedByType: fbsvc.CreatedBySystem, + ThreadType: fbsvc.ThreadTypeStandard, + FirstPostBodyText: bodyText, + FirstPostBody: richBody, + ReferencedLeague: "nfl", + ExternalEventKey: eventKey, + } + + thread, err := fbsvc.CreateThread(ctx, input) + if err != nil { + log.Printf("ForumManager: failed to create NFL UDFA sync thread for season %d: %v", season, err) + return + } + + log.Printf("ForumManager: created NFL UDFA sync thread %s for season %d", thread.ID, season) +} + +func buildNFLUDFASyncParagraphs(season int, signings []string) []string { + var paragraphs []string + + if len(signings) == 0 { + paragraphs = append(paragraphs, + fmt.Sprintf( + "The Undrafted Free Agency period has concluded for Season %d. No players were signed.", + season, + ), + ) + } else { + paragraphs = append(paragraphs, + fmt.Sprintf( + "The Undrafted Free Agency period has concluded for Season %d! The following players have signed with new teams:", + season, + ), + ) + for _, label := range signings { + paragraphs = append(paragraphs, label) + } + } + + paragraphs = append(paragraphs, "Discuss the latest UDFA additions and how they fit into their new rosters below!") + + return paragraphs +} diff --git a/managers/UDFAManager.go b/managers/UDFAManager.go index 3c9eb39..3559c42 100644 --- a/managers/UDFAManager.go +++ b/managers/UDFAManager.go @@ -3,6 +3,7 @@ package managers import ( "fmt" "math/rand" + "strconv" "time" "github.com/CalebRose/SimFBA/dbprovider" @@ -10,12 +11,78 @@ import ( "github.com/CalebRose/SimFBA/structs" ) +// ------------------------------------------------------------------------ +// USER BOARD MANAGEMENT (The functions your Controller was looking for) +// ------------------------------------------------------------------------ + +func GetUDFABoardByTeamID(teamID string) structs.NFLUDFABoard { + db := dbprovider.GetInstance().GetDB() + var board structs.NFLUDFABoard + + // Find the board and load the players currently on it + db.Preload("Profiles").Where("team_id = ?", teamID).Find(&board) + + // If the team doesn't have a board yet, create an empty one + if board.ID == 0 { + var team structs.NFLTeam + db.Where("id = ?", teamID).Find(&team) + + board = structs.NFLUDFABoard{ + TeamID: team.ID, + TeamAbbr: team.TeamAbbr, + } + db.Create(&board) + } + + return board +} + +func AddPlayerToUDFABoard(dto structs.NFLUDFAProfile) { + db := dbprovider.GetInstance().GetDB() + + // Get the user's board + board := GetUDFABoardByTeamID(strconv.Itoa(int(dto.TeamID))) + + // Check if this player is already on the board to prevent duplicates + var existing structs.NFLUDFAProfile + db.Where("nfl_udfa_board_id = ? AND player_id = ?", board.ID, dto.PlayerID).Find(&existing) + + if existing.ID == 0 { + dto.NFLUDFABoardID = board.ID + db.Create(&dto) + } +} + +func SaveUDFABoard(dto structs.NFLUDFABoard) { + db := dbprovider.GetInstance().GetDB() + + // Loop through the submitted board and update the points for each player + for _, profile := range dto.Profiles { + var existing structs.NFLUDFAProfile + db.Where("id = ?", profile.ID).Find(&existing) + + if existing.ID > 0 { + existing.Points = profile.Points + db.Save(&existing) + } + } +} + +func RemovePlayerFromUDFABoard(profileID string) { + db := dbprovider.GetInstance().GetDB() + db.Where("id = ?", profileID).Delete(&structs.NFLUDFAProfile{}) +} + +// ------------------------------------------------------------------------ +// ADMIN BATCH PROCESSING (The logic to actually sign the players) +// ------------------------------------------------------------------------ + func ProcessUDFAs(isDryRun bool) { db := dbprovider.GetInstance().GetDB() // 1. Get all Undrafted Players var undraftedPlayers []models.NFLDraftee - db.Where("draft_pick_id = 0").Find(&undraftedPlayers) + db.Where("draft_pick_id = 0 AND drafted_team_id = 0").Find(&undraftedPlayers) // 2. Get all Bids var allBids []structs.NFLUDFAProfile @@ -27,10 +94,13 @@ func ProcessUDFAs(isDryRun bool) { bidsByPlayer[bid.PlayerID] = append(bidsByPlayer[bid.PlayerID], bid) } + // NEW: Create a map to group signings by Team for the Forum Post + teamSignings := make(map[string][]string) + for _, player := range undraftedPlayers { bids := bidsByPlayer[player.ID] if len(bids) == 0 { - continue + continue // No one bid on this player } // Find winning bid (Highest points) @@ -54,29 +124,57 @@ func ProcessUDFAs(isDryRun bool) { winningBid = tiedBids[rand.Intn(len(tiedBids))] } + // Execute if !isDryRun && winningBid.Points > 0 { SignUDFA(player, winningBid) + + // NEW: Record the signing for the forum post + playerString := fmt.Sprintf("%s %s %s", player.Position, player.FirstName, player.LastName) + teamSignings[winningBid.TeamAbbr] = append(teamSignings[winningBid.TeamAbbr], playerString) + } else if isDryRun { fmt.Printf("DRY RUN: %s %s would sign with %s for %d points\n", player.FirstName, player.LastName, winningBid.TeamAbbr, winningBid.Points) } } + + // NEW: Generate the Forum Post if it was a live run and players were signed! + if !isDryRun && len(teamSignings) > 0 { + // Get the current NFL Season ID from the Timestamp + ts := GetTimestamp() + + var forumSignings []string + + for teamAbbr, players := range teamSignings { + // Add the Team abbreviation as a bolded paragraph line + forumSignings = append(forumSignings, fmt.Sprintf("**%s**", teamAbbr)) + + // Add each player as a bullet point below the team + for _, p := range players { + forumSignings = append(forumSignings, fmt.Sprintf("• %s", p)) + } + } + + // Fire off the automated post to Firebase! + // CreateNFLUDFASyncForumThread(ts.NFLSeasonID, forumSignings) - COMMENTED OUT FOR LOCALTEST + } } func SignUDFA(draftee models.NFLDraftee, bid structs.NFLUDFAProfile) { db := dbprovider.GetInstance().GetDB() - // Convert Draftee to NFLPlayer + // Convert Draftee to an active NFLPlayer nflPlayer := structs.NFLPlayer{ - BasePlayer: draftee.BasePlayer, - TeamID: int(bid.TeamID), - TeamAbbr: bid.TeamAbbr, - Experience: 1, - IsActive: true, + BasePlayer: draftee.BasePlayer, + TeamID: int(bid.TeamID), + TeamAbbr: bid.TeamAbbr, + Experience: 1, + IsActive: true, + IsFreeAgent: false, } nflPlayer.ID = draftee.ID db.Create(&nflPlayer) - // Create 3-year contract: 0.5 Salary, 0 Bonus + // Create 3-year contract: 0.5M Salary, 0 Bonus contract := structs.NFLContract{ NFLPlayerID: int(nflPlayer.ID), TeamID: bid.TeamID, @@ -87,8 +185,9 @@ func SignUDFA(draftee models.NFLDraftee, bid structs.NFLUDFAProfile) { Y3BaseSalary: 0.5, IsActive: true, } + contract.CalculateContract() // Updates the AAV and total values db.Create(&contract) - // Delete from draftee table + // Delete from draftee table so they don't show up in the draft pool anymore db.Delete(&draftee) -} \ No newline at end of file +} From a6858212a48a5923b3835dd84da35ad825cb1877 Mon Sep 17 00:00:00 2001 From: ChrisSaterra Date: Thu, 7 May 2026 22:59:24 +0100 Subject: [PATCH 3/6] Forum Post Writer + Unsigned UDFA's to FA solved --- managers/UDFAManager.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/managers/UDFAManager.go b/managers/UDFAManager.go index 3559c42..526107d 100644 --- a/managers/UDFAManager.go +++ b/managers/UDFAManager.go @@ -159,6 +159,30 @@ func ProcessUDFAs(isDryRun bool) { } } +// SendToFreeAgency officially moves an unsigned UDFA into the Free Agency pool +// and sets their minimum contract demands. +func SendToFreeAgency(draftee models.NFLDraftee) { + db := dbprovider.GetInstance().GetDB() + + // 1. Fetch the actual NFLPlayer record associated with this Draftee + var proPlayer structs.NFLPlayer + db.Where("player_id = ?", draftee.PlayerID).Find(&proPlayer) + + // If we somehow can't find the pro player record, abort to prevent a crash + if proPlayer.ID == 0 { + return + } + + // 2. Set the minimum contract demands on the PRO player record + proPlayer.MinimumValue = 0.5 + proPlayer.AAV = 0.5 + proPlayer.IsFreeAgent = true + proPlayer.IsAcceptingOffers = true + + // 3. Save the updated PRO player to the database + db.Save(&proPlayer) +} + func SignUDFA(draftee models.NFLDraftee, bid structs.NFLUDFAProfile) { db := dbprovider.GetInstance().GetDB() From 91ef153a12d647a60acf254bf64092cf83be958d Mon Sep 17 00:00:00 2001 From: ChrisSaterra Date: Fri, 8 May 2026 00:23:37 +0100 Subject: [PATCH 4/6] Fix to UDFA Manager - Commented out Database writer. Needs solving for live --- managers/UDFAManager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/managers/UDFAManager.go b/managers/UDFAManager.go index 526107d..8ffc71c 100644 --- a/managers/UDFAManager.go +++ b/managers/UDFAManager.go @@ -180,7 +180,7 @@ func SendToFreeAgency(draftee models.NFLDraftee) { proPlayer.IsAcceptingOffers = true // 3. Save the updated PRO player to the database - db.Save(&proPlayer) + // db.Save(&proPlayer) - COMMENTED OUT FOR LOCAL TEST } func SignUDFA(draftee models.NFLDraftee, bid structs.NFLUDFAProfile) { From 2d4a2d98b215c4894f2e33025e16aed684163006 Mon Sep 17 00:00:00 2001 From: ChrisSaterra Date: Fri, 8 May 2026 01:03:25 +0100 Subject: [PATCH 5/6] Commented out ts. Needs adding on live use day --- managers/UDFAManager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/managers/UDFAManager.go b/managers/UDFAManager.go index 8ffc71c..5eae44f 100644 --- a/managers/UDFAManager.go +++ b/managers/UDFAManager.go @@ -140,7 +140,7 @@ func ProcessUDFAs(isDryRun bool) { // NEW: Generate the Forum Post if it was a live run and players were signed! if !isDryRun && len(teamSignings) > 0 { // Get the current NFL Season ID from the Timestamp - ts := GetTimestamp() + // ts := GetTimestamp() var forumSignings []string From 29b59d6e0d562e6f905a8e08cc07dcb741a1c38a Mon Sep 17 00:00:00 2001 From: CalebRose Date: Sat, 9 May 2026 10:56:18 -0700 Subject: [PATCH 6/6] adding db tables for udfa board and to ts models --- controller/TSController.go | 1 + dbprovider/dbprovider.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/controller/TSController.go b/controller/TSController.go index 2675019..52e60b1 100644 --- a/controller/TSController.go +++ b/controller/TSController.go @@ -90,6 +90,7 @@ func CreateTSModelsFile(w http.ResponseWriter, r *http.Request) { Add(structs.CollegeGame{}). Add(structs.NFLCapsheet{}). Add(structs.NFLContract{}). + Add(structs.NFLUDFABoard{}). Add(structs.FreeAgencyOffer{}). Add(structs.FreeAgencyOfferDTO{}). Add(structs.NFLWaiverOffDTO{}). diff --git a/dbprovider/dbprovider.go b/dbprovider/dbprovider.go index 7356ce4..330ebfc 100644 --- a/dbprovider/dbprovider.go +++ b/dbprovider/dbprovider.go @@ -146,6 +146,8 @@ func (p *Provider) InitDatabase() bool { // db.AutoMigrate(&structs.NFLStandings{}) // db.AutoMigrate(&structs.NFLRequest{}) // db.AutoMigrate(&structs.NFLWaiverOffer{}) + // db.AutoMigrate(&structs.NFLUDFABoard{}) + // db.AutoMigrate(&structs.NFLUDFAProfile{}) // All // db.AutoMigrate(&structs.AdminRecruitModifier{})