Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .github/workflows/main_fforacle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
dotnet-version: "8.x"

- name: Build project
run: dotnet build Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.csproj --configuration Release
run: dotnet build FFOracle/FFOracle.csproj --configuration Release

- name: Publish project
run: dotnet publish Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.csproj -c Release -o ./publish
run: dotnet publish FFOracle/FFOracle.csproj -c Release -o ./publish

- name: Upload artifact for deployment
uses: actions/upload-artifact@v4
Expand All @@ -52,9 +52,7 @@ jobs:
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_3C280D050E0F4243A338A118756AF2E5 }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_E6C08C7D922B4E519DEFD6ACD7A93981 }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_58AD54B4E954416F8314B16D46FE3B02 }}
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Deploy to Azure Web App
id: deploy-to-webapp
Expand Down
71 changes: 71 additions & 0 deletions FFOracle/Controllers/AutoUpdateController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using FFOracle.Services;
using Microsoft.AspNetCore.Mvc;
using Supabase;


namespace FFOracle.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AutoUpdateController: ControllerBase
{
private readonly NflVerseService _nflVerseService;
private readonly Client _supabase;
private readonly UpdateSupabaseService _updateSupabaseService;

public AutoUpdateController(NflVerseService nflVerseService, Client supabase)
{
_nflVerseService = nflVerseService;
_supabase = supabase;
_updateSupabaseService = new UpdateSupabaseService(_nflVerseService, _supabase);
}

// PUT route for weekly updates
[HttpPut("weekly")]
public async Task<IActionResult> PutWeekly()
{
try
{
// update weekly player stats (add the new week)
await _updateSupabaseService.UpdateStatsFromLastThreeWeeksAsync();

// update the season player stats
await _updateSupabaseService.UpdateAllPlayerSeasonStatsAsync();

// update the non stat data for players (e.g. status)
await _updateSupabaseService.UpdateAllPlayerNonStatDataAsync();

// update the team season stats
await _updateSupabaseService.UpdateAllTeamSeasonStatsAsync();

// update the games this week (delete and add new games)
await _updateSupabaseService.UpdateGamesThisWeekAsync();

return Ok();
}
catch (Exception ex)
{
return StatusCode(500, $"Error performing the weekly update: {ex.Message}");
}
}

// PUT route for daily updates (player injury status, game odds)
[HttpPut("daily")]
public async Task<IActionResult> PutDaily()
{
try
{
// update the non stat data for players (e.g. status)
await _updateSupabaseService.UpdateAllPlayerNonStatDataAsync();

// update the games this week (delete and add new games)
await _updateSupabaseService.UpdateGamesThisWeekAsync();

return Ok();
}
catch (Exception ex)
{
return StatusCode(500, $"Error performing the weekly update: {ex.Message}");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Fantasy_Football_Assistant_Manager.Services;
using FFOracle.Services;

namespace Fantasy_Football_Assistant_Manager.Controllers;
namespace FFOracle.Controllers;

[ApiController]
[Route("api/[controller]")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
using Fantasy_Football_Assistant_Manager.Models.Supabase;
using Fantasy_Football_Assistant_Manager.Services;
using Fantasy_Football_Assistant_Manager.Utils;
using FFOracle.Models.Supabase;
using FFOracle.Services;
using FFOracle.Utils;
using Microsoft.AspNetCore.Mvc;
using Supabase;

namespace Fantasy_Football_Assistant_Manager.Controllers;
namespace FFOracle.Controllers;

[ApiController]
[Route("api/[controller]")]
public class GamesThisWeekController : ControllerBase
{
private readonly NflVerseService _nflVerseService;
private readonly Client _supabase;
private readonly UpdateSupabaseService _updateSupabaseService;

public GamesThisWeekController(NflVerseService nflVerseService, Client supabase)
{
_nflVerseService = nflVerseService;
_supabase = supabase;
_updateSupabaseService = new UpdateSupabaseService(_nflVerseService, _supabase);
}

[HttpPost("all")]
Expand Down Expand Up @@ -76,50 +78,7 @@ public async Task<IActionResult> PutAll()
{
try
{
// get current season and week from supabase
var (currentSeason, currentWeek) = await ControllerHelpers.GetCurrentSeasonAndWeekAsync(_supabase);

// get game data from nfl verse service
var games = await _nflVerseService.GetAllGamesThisWeekAsync(currentSeason, currentWeek);

if (games == null || !games.Any())
{
return BadRequest(new { message = "No games found for current week." });
}

// map GameThisWeekCsv to GameThisWeek supabase model
var gamesToInsert = games.Select(g => new GameThisWeek
{
Id = Guid.NewGuid(),
HomeTeam = g.HomeTeam,
AwayTeam = g.AwayTeam,
Weekday = g.Weekday,
GameDateTime = g.GameDateTime,
StadiumName = g.StadiumName,
StadiumStyle = g.StadiumStyle,
IsDivisionalGame = g.IsDivisionalGame,
HomeRestDays = g.HomeRestDays,
AwayRestDays = g.AwayRestDays,
HomeMoneyline = g.HomeMoneyline,
AwayMoneyline = g.AwayMoneyline,
HomeSpreadOdds = g.HomeSpreadOdds,
AwaySpreadOdds = g.AwaySpreadOdds,
SpreadLine = g.SpreadLine,
TotalLine = g.TotalLine,
UnderOdds = g.UnderOdds,
OverOdds = g.OverOdds
}).ToList();

// clear the games this week table and repopulate it
await _supabase
.From<GameThisWeek>()
.Where(g => g.Id != null)
.Delete();

await _supabase
.From<GameThisWeek>()
.Insert(gamesToInsert);

await _updateSupabaseService.UpdateGamesThisWeekAsync();
return Ok(new { message = "Successfully updated all games this week" });
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
using Fantasy_Football_Assistant_Manager.Models.Supabase;
using Fantasy_Football_Assistant_Manager.Services;
using Fantasy_Football_Assistant_Manager.Utils;
using FFOracle.Models.Supabase;
using FFOracle.Services;
using FFOracle.Utils;
using Microsoft.AspNetCore.Mvc;
using Supabase;

namespace Fantasy_Football_Assistant_Manager.Controllers;
namespace FFOracle.Controllers;

[ApiController]
[Route("api/[controller]")]
public class PlayerSeasonStatController: ControllerBase
{
private readonly NflVerseService _nflVerseService;
private readonly Client _supabase;
private readonly UpdateSupabaseService _updateSupabaseService;

// injects NflVerseService via dependency injection
public PlayerSeasonStatController(NflVerseService nflVerseService, Client supabase)
{
_nflVerseService = nflVerseService;
_supabase = supabase;
_updateSupabaseService = new UpdateSupabaseService(_nflVerseService, _supabase);
}

// DELETE route for testing
Expand Down Expand Up @@ -125,63 +127,7 @@ public async Task<IActionResult> PutAll()
{
try
{
// fetch offensive players using service
var seasonStats = await _nflVerseService.GetAllOffensivePlayerSeasonStatsAsync();

if (seasonStats == null || !seasonStats.Any())
{
return BadRequest("No season stats found to insert.");
}

// get pairs of player_id and their season_stats_id
var playerIds = seasonStats.Select(s => s.PlayerId).ToList();

var playersResponse = await _supabase
.From<Player>()
.Select(p => new object[] { p.Id, p.SeasonStatsId })
.Filter("id", Supabase.Postgrest.Constants.Operator.In, playerIds)
.Get();

var playerStatMap = playersResponse.Models
.Where(p => p.SeasonStatsId != null)
.ToDictionary(p => p.Id, p => p.SeasonStatsId.Value);

// Build list of updates
var updates = seasonStats.Select(s => new
{
season_stats_id = playerStatMap[s.PlayerId],
completions = ControllerHelpers.NullIfZero(s.Completions),
passing_attempts = ControllerHelpers.NullIfZero(s.PassingAttempts),
passing_yards = ControllerHelpers.NullIfZero(s.PassingYards),
passing_tds = ControllerHelpers.NullIfZero(s.PassingTds),
interceptions_against = ControllerHelpers.NullIfZero(s.InterceptionsAgainst),
sacks_against = ControllerHelpers.NullIfZero(s.SacksAgainst),
fumbles_against = ControllerHelpers.NullIfZero(s.FumblesAgainst),
passing_first_downs = ControllerHelpers.NullIfZero(s.PassingFirstDowns),
passing_epa = ControllerHelpers.NullIfZero(s.PassingEpa),
carries = ControllerHelpers.NullIfZero(s.Carries),
rushing_yards = ControllerHelpers.NullIfZero(s.RushingYards),
rushing_tds = ControllerHelpers.NullIfZero(s.RushingTds),
rushing_first_downs = ControllerHelpers.NullIfZero(s.RushingFirstDowns),
rushing_epa = ControllerHelpers.NullIfZero(s.RushingEpa),
receptions = ControllerHelpers.NullIfZero(s.Receptions),
targets = ControllerHelpers.NullIfZero(s.Targets),
receiving_yards = ControllerHelpers.NullIfZero(s.ReceivingYards),
receiving_tds = ControllerHelpers.NullIfZero(s.ReceivingTds),
receiving_first_downs = ControllerHelpers.NullIfZero(s.ReceivingFirstDowns),
receiving_epa = ControllerHelpers.NullIfZero(s.ReceivingEpa),
fg_made_list = s.FgMadeList,
fg_missed_list = s.FgMissedList,
fg_blocked_list = s.FgBlockedList,
pat_attempts = ControllerHelpers.NullIfZero(s.PadAttempts),
pat_percent = ControllerHelpers.NullIfZero(s.PatPercent),
fantasy_points = s.FantasyPoints,
fantasy_points_ppr = s.FantasyPointsPpr
}).ToList();

// Call RPC to update all at once
await _supabase.Rpc("batch_update_player_season_stats", new { updates });

await _updateSupabaseService.UpdateAllPlayerSeasonStatsAsync();
return Ok(new { message = "Season stats updated successfully!" });
}
catch (Exception ex)
Expand Down
92 changes: 92 additions & 0 deletions FFOracle/Controllers/PlayerWeeklyStatController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using FFOracle.Models.Supabase;
using FFOracle.Services;
using FFOracle.Utils;
using Microsoft.AspNetCore.Mvc;
using Supabase;

namespace FFOracle.Controllers;

[ApiController]
[Route("api/[controller]")]
public class PlayerWeeklyStatController : ControllerBase
{
private readonly NflVerseService _nflVerseService;
private readonly Client _supabase;
private readonly UpdateSupabaseService _updateSupabaseService;

// injects NflVerseService via dependency injection
public PlayerWeeklyStatController(NflVerseService nflVerseService, Client supabase)
{
_nflVerseService = nflVerseService;
_supabase = supabase;
_updateSupabaseService = new UpdateSupabaseService(_nflVerseService, _supabase);
}

// POST route for adding all weekly player stats up to the most recent week
// to be called from Azure Functions App on a schedule for weekly updates
[HttpPost("all")]
public async Task<IActionResult> PostAll()
{
try
{
var (startWeek, endWeek) = await _updateSupabaseService.UpdateStatsFromLastThreeWeeksAsync();
return Ok(new { message = $"Inserted weekly stats successfully for weeks {startWeek}–{endWeek}" });
}
catch (Exception ex)
{
return StatusCode(500, $"Error inserting weekly stats: {ex.Message}");
}
}

// DELETE route for deleting all weekly player stats
// used for testing
[HttpDelete("all")]
public async Task<IActionResult> DeleteAll()
{
try
{
bool moreRecordsToDelete = true;
var batchSize = 500;

// supabase cannot handle deleting thousands or records at once, so break up into batches
while (moreRecordsToDelete)
{
// get all PlayerStats to delete from player_stats table
var playerStatsResponse = await _supabase
.From<WeeklyPlayerStat>()
.Select("player_stats_id")
.Limit(batchSize)
.Get();

var batchIds = playerStatsResponse.Models
.Select(s => s.PlayerStatsId)
.ToList();

// break if deleted all records
if (!batchIds.Any())
{
moreRecordsToDelete = false;
break;
}

// delete all batch records from player_stats table
await _supabase
.From<PlayerStat>()
.Filter("id", Supabase.Postgrest.Constants.Operator.In, batchIds)
.Delete();

// delete all batch records from weekly_player_stats table
await _supabase
.From<WeeklyPlayerStat>()
.Filter("player_stats_id", Supabase.Postgrest.Constants.Operator.In, batchIds)
.Delete();
}

return Ok(new { message = "Successfully deleted all weekly stats" });
}
catch (Exception ex)
{
return StatusCode(500, $"Error deleting weekly stats: {ex.Message}");
}
}
}
Loading