diff --git a/.github/workflows/main_fforacle.yml b/.github/workflows/main_fforacle.yml index 67d899f..9fd2eb4 100644 --- a/.github/workflows/main_fforacle.yml +++ b/.github/workflows/main_fforacle.yml @@ -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 @@ -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 diff --git a/FFOracle/Controllers/AutoUpdateController.cs b/FFOracle/Controllers/AutoUpdateController.cs new file mode 100644 index 0000000..1c3180b --- /dev/null +++ b/FFOracle/Controllers/AutoUpdateController.cs @@ -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 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 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}"); + } + } +} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/ChatGPTController.cs b/FFOracle/Controllers/ChatGPTController.cs similarity index 90% rename from Fantasy-Football-Assistant-Manager/Controllers/ChatGPTController.cs rename to FFOracle/Controllers/ChatGPTController.cs index 7a2d05c..2c61401 100644 --- a/Fantasy-Football-Assistant-Manager/Controllers/ChatGPTController.cs +++ b/FFOracle/Controllers/ChatGPTController.cs @@ -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]")] diff --git a/Fantasy-Football-Assistant-Manager/Controllers/GamesThisWeekController.cs b/FFOracle/Controllers/GamesThisWeekController.cs similarity index 61% rename from Fantasy-Football-Assistant-Manager/Controllers/GamesThisWeekController.cs rename to FFOracle/Controllers/GamesThisWeekController.cs index 2e86a92..1e507c4 100644 --- a/Fantasy-Football-Assistant-Manager/Controllers/GamesThisWeekController.cs +++ b/FFOracle/Controllers/GamesThisWeekController.cs @@ -1,10 +1,10 @@ -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]")] @@ -12,11 +12,13 @@ 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")] @@ -76,50 +78,7 @@ public async Task 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() - .Where(g => g.Id != null) - .Delete(); - - await _supabase - .From() - .Insert(gamesToInsert); - + await _updateSupabaseService.UpdateGamesThisWeekAsync(); return Ok(new { message = "Successfully updated all games this week" }); } catch (Exception ex) diff --git a/Fantasy-Football-Assistant-Manager/Controllers/PlayerSeasonStatController.cs b/FFOracle/Controllers/PlayerSeasonStatController.cs similarity index 61% rename from Fantasy-Football-Assistant-Manager/Controllers/PlayerSeasonStatController.cs rename to FFOracle/Controllers/PlayerSeasonStatController.cs index 15abdad..0ebfb73 100644 --- a/Fantasy-Football-Assistant-Manager/Controllers/PlayerSeasonStatController.cs +++ b/FFOracle/Controllers/PlayerSeasonStatController.cs @@ -1,10 +1,10 @@ -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]")] @@ -12,12 +12,14 @@ 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 @@ -125,63 +127,7 @@ public async Task 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() - .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) diff --git a/FFOracle/Controllers/PlayerWeeklyStatController.cs b/FFOracle/Controllers/PlayerWeeklyStatController.cs new file mode 100644 index 0000000..68d8125 --- /dev/null +++ b/FFOracle/Controllers/PlayerWeeklyStatController.cs @@ -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 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 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() + .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() + .Filter("id", Supabase.Postgrest.Constants.Operator.In, batchIds) + .Delete(); + + // delete all batch records from weekly_player_stats table + await _supabase + .From() + .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}"); + } + } +} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/PlayersController.cs b/FFOracle/Controllers/PlayersController.cs similarity index 71% rename from Fantasy-Football-Assistant-Manager/Controllers/PlayersController.cs rename to FFOracle/Controllers/PlayersController.cs index a8272e4..d41d7a2 100644 --- a/Fantasy-Football-Assistant-Manager/Controllers/PlayersController.cs +++ b/FFOracle/Controllers/PlayersController.cs @@ -1,10 +1,10 @@ -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]")] @@ -12,12 +12,14 @@ public class PlayersController: ControllerBase { private readonly NflVerseService _nflVerseService; private readonly Client _supabase; + private readonly UpdateSupabaseService _updateSupabaseService; // injects NflVerseService via dependency injection public PlayersController(NflVerseService nflVerseService, Client supabase) { _nflVerseService = nflVerseService; _supabase = supabase; + _updateSupabaseService = new UpdateSupabaseService(_nflVerseService, _supabase); } // ALL PLAYERS ROUTES @@ -139,61 +141,7 @@ public async Task PutAll() { try { - // get all player ids from supabase - var response = await _supabase - .From() - .Get(); - - if (response.Models == null || !response.Models.Any()) - { - return NotFound("No players found in database."); - } - - var existingPlayers = response.Models; - var playerLookup = existingPlayers.ToDictionary(p => p.Id, StringComparer.OrdinalIgnoreCase); - - // get the current season and week from supabase - var (currentSeason, currentWeek) = await ControllerHelpers.GetCurrentSeasonAndWeekAsync(_supabase); - - // get all player information from nflverse service - var playerInformation = await _nflVerseService.GetAllPlayerInformationAsync(currentSeason); - - if (playerInformation == null || !playerInformation.Any()) - { - return NotFound("No player information found."); - } - - // merge info only for players that exist in Supabase - var filteredPlayerWithInfo = playerInformation - .Where(info => !string.IsNullOrWhiteSpace(info.Id) && playerLookup.ContainsKey(info.Id)) - .Select(info => - { - var existingPlayer = playerLookup[info.Id]; - return new Player - { - Id = existingPlayer.Id, - Name = existingPlayer.Name, - HeadshotUrl = existingPlayer.HeadshotUrl, - Position = existingPlayer.Position, - Status = info.Status, - StatusDescription = info.ShortDescription, - TeamId = info.LatestTeam, - SeasonStatsId = existingPlayer.SeasonStatsId - }; - }) - .ToList(); - - if (!filteredPlayerWithInfo.Any()) - { - return BadRequest("No matching players to update."); - } - - // update Supabase players table - var updateResponse = await _supabase - .From() - .OnConflict(x => x.Id) - .Upsert(filteredPlayerWithInfo); - + await _updateSupabaseService.UpdateAllPlayerNonStatDataAsync(); return Ok(new { message = "Player information updated successfully!" }); } catch (Exception ex) diff --git a/FFOracle/Controllers/StripeController.cs b/FFOracle/Controllers/StripeController.cs new file mode 100644 index 0000000..462f516 --- /dev/null +++ b/FFOracle/Controllers/StripeController.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Mvc; +using Stripe; + +namespace FFOracle.Controllers; + + +[ApiController] //establishes that class will handle API requests +[Route("api/[controller]")] //establishes this controller's base URL path +public class StripeController : Controller //Controller class gives access to objects/methods needed to handle HTTP +{ + private readonly IConfiguration _config; //accesses appsettings information, including keys + + //constructor + public StripeController(IConfiguration config) + { + _config = config; + } + + //endpoint for creating a payment intent + //The [FromBody] tag tells the code that this method will receive data from a passed in JSON file. + // The file contents are then automatically mapped to the attributes of the argument type. + [HttpPost("create-payment-intent")] //establishes that post requests will come in at this URL + public async Task CreatePaymentIntent([FromBody] PaymentRequest request) + { + //The payment intent is an object that is carried across the entire payment process. + //It holds information about a specific payment and tracks the status of that payment. + + StripeConfiguration.ApiKey = _config["Stripe:SecretKey"]; //provide API key + + //Collect information on the new payment + var options = new PaymentIntentCreateOptions + { + Amount = request.Amount, // amount to pay; for USD, it is in cents + Currency = "usd", //currency denomination + PaymentMethodTypes = new List { "card" }, //payment types to offer + }; + + var service = new PaymentIntentService(); //PaymentIntentService is a class for interfacing with the API + var paymentIntent = await service.CreateAsync(options); //request a new payment intent from the Stripe servers + + //return a success response. Do not pass the payment intent to the client, just sent the client secret. + //The client secret is a frontend-safe identifier for a transaction/payment intent. + return Ok(new { clientSecret = paymentIntent.ClientSecret }); + } +} + +//class to contain payment quantity information. Populated by JSON sent from client and passed +// to controller as input. +public class PaymentRequest +{ + public long Amount { get; set; } +} diff --git a/FFOracle/Controllers/StripeWebhookController.cs b/FFOracle/Controllers/StripeWebhookController.cs new file mode 100644 index 0000000..2e1ed21 --- /dev/null +++ b/FFOracle/Controllers/StripeWebhookController.cs @@ -0,0 +1,92 @@ +using Microsoft.AspNetCore.Mvc; +using Stripe; +using static Microsoft.IO.RecyclableMemoryStreamManager; + +namespace FFOracle.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class StripeWebhookController : Controller +{ + + private readonly ILogger _logger; //a logger I'll use to indicate successful payments + private readonly IConfiguration _config; //accesses appsettings info + + //constructor to initialize the stored logger and config + public StripeWebhookController(ILogger logger, IConfiguration config) + { + _logger = logger; + _config = config; + } + + [HttpPost("check-payment-completion")] + public async Task HandleStripeWebhook() + { + //Read the request body + var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); + + //retrieve the webhook secret. + //As of now, WE HAVE NO WEBHOOK ENDPOINT WITH STRIPE. That requires a proper public URL for our + // backend. I have used the stripe CLI to make a temporary bridge from which we can get responses. + var webhookSecret = _config["Stripe:WebhookSecret"]; + //verify that it's actually there + if (string.IsNullOrEmpty(webhookSecret)) + { + _logger.LogError("Stripe WebhookSecret is not configured."); + return BadRequest("Webhook secret not configured."); + } + + try + { + //Parse the message into an Event object and ensure it's a valid stripe message + var stripeEvent = EventUtility.ConstructEvent( + json, + Request.Headers["Stripe-Signature"], //use this to verify that it has the stripe header + webhookSecret + ); + + //Each case of the switch statement handles a different event type. + switch (stripeEvent.Type) + { + case "payment_intent.succeeded": + { + //for now, just log the success + var paymentIntent = stripeEvent.Data.Object as PaymentIntent; + _logger.LogInformation("PaymentIntent succeeded: {Id}, amount: {Amount}", paymentIntent.Id, paymentIntent.Amount); + //This is where DB would be updated + break; + } + + case "payment_intent.payment_failed": + { + //for now, just log the failure + var paymentIntent = stripeEvent.Data.Object as PaymentIntent; + _logger.LogWarning("PaymentIntent failed: {Id}", paymentIntent.Id); + //This is where a user notification might be called + break; + } + //I can add other events here as needed, but for now we only need those two + + //Log any event aside from the ones expected + default: + _logger.LogInformation("Unhandled Stripe event type: {Type}", stripeEvent.Type); + break; + } + + //This response tells stripe the message was received + return Ok(); + } + catch (StripeException ex) + { + //If anything goes wrong in validating the message, log it and tell stripe a bad message was received + _logger.LogError(ex, "Stripe webhook handling failed: {Message}", ex.Message); + return BadRequest(); + } + catch (System.Exception ex) + { + //Catch other errors + _logger.LogError(ex, "Unexpected error handling Stripe webhook"); + return StatusCode(500); + } + } +} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseController.cs b/FFOracle/Controllers/SupaBaseController.cs similarity index 98% rename from Fantasy-Football-Assistant-Manager/Controllers/SupaBaseController.cs rename to FFOracle/Controllers/SupaBaseController.cs index 7ca8ea1..1d6fd02 100644 --- a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseController.cs +++ b/FFOracle/Controllers/SupaBaseController.cs @@ -1,8 +1,8 @@ -using Fantasy_Football_Assistant_Manager.Models.Supabase; +using FFOracle.Models.Supabase; using Microsoft.AspNetCore.Mvc; using Supabase; -namespace Fantasy_Football_Assistant_Manager.Controllers; +namespace FFOracle.Controllers; [ApiController] [Route("api/[controller]")] diff --git a/Fantasy-Football-Assistant-Manager/Controllers/TeamController.cs b/FFOracle/Controllers/TeamController.cs similarity index 92% rename from Fantasy-Football-Assistant-Manager/Controllers/TeamController.cs rename to FFOracle/Controllers/TeamController.cs index cb29613..e788e00 100644 --- a/Fantasy-Football-Assistant-Manager/Controllers/TeamController.cs +++ b/FFOracle/Controllers/TeamController.cs @@ -1,10 +1,10 @@ -using Fantasy_Football_Assistant_Manager.Models.Supabase; -using Fantasy_Football_Assistant_Manager.Services; +using FFOracle.Models.Supabase; +using FFOracle.Services; using Microsoft.AspNetCore.Mvc; using Supabase; -namespace Fantasy_Football_Assistant_Manager.Controllers; +namespace FFOracle.Controllers; [ApiController] [Route("api/[controller]")] diff --git a/FFOracle/Controllers/TeamSeasonStatController.cs b/FFOracle/Controllers/TeamSeasonStatController.cs new file mode 100644 index 0000000..587ad2e --- /dev/null +++ b/FFOracle/Controllers/TeamSeasonStatController.cs @@ -0,0 +1,60 @@ +using FFOracle.Models.Supabase; +using FFOracle.Services; +using FFOracle.Utils; +using Microsoft.AspNetCore.Mvc; +using Supabase; + +namespace FFOracle.Controllers; + +public class TeamSeasonStatController : ControllerBase +{ + private readonly NflVerseService _nflVerseService; + private readonly Client _supabase; + private readonly UpdateSupabaseService _updateSupabaseService; + + public TeamSeasonStatController(NflVerseService nflVerseService, Client supabase) + { + _nflVerseService = nflVerseService; + _supabase = supabase; + _updateSupabaseService = new UpdateSupabaseService(_nflVerseService, _supabase); + } + + [HttpPost("all")] + public async Task PostAll() + { + try + { + await _updateSupabaseService.UpdateAllTeamSeasonStatsAsync(); + return Ok(new { message = "Team stats inserted successfully!" }); + } + catch (Exception ex) + { + return StatusCode(500, $"Error populating team stats: {ex.Message}"); + } + } + + [HttpDelete("all")] + public async Task DeleteAll() + { + try + { + // delete all offensive stats + await _supabase + .From() + .Where(s => s.Id != null) + .Delete(); + + // delete all defensive stats + await _supabase + .From() + .Where(s => s.Id != null) + .Delete(); + + // the foreign keys set to null automatically in teams table + return Ok(new { message = "Successfully deleted all team stats" }); + } catch (Exception ex) + { + return StatusCode(500, $"Error deleting all team stats: {ex.Message}"); + } + } +} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/UsersController.cs b/FFOracle/Controllers/UsersController.cs similarity index 96% rename from Fantasy-Football-Assistant-Manager/Controllers/UsersController.cs rename to FFOracle/Controllers/UsersController.cs index fad0ad8..cd8d5d0 100644 --- a/Fantasy-Football-Assistant-Manager/Controllers/UsersController.cs +++ b/FFOracle/Controllers/UsersController.cs @@ -1,7 +1,7 @@ -using Fantasy_Football_Assistant_Manager.DTOs; +using FFOracle.DTOs; using Microsoft.AspNetCore.Mvc; -namespace Fantasy_Football_Assistant_Manager.Controllers; +namespace FFOracle.Controllers; [ApiController] [Route("api/[controller]")] diff --git a/Fantasy-Football-Assistant-Manager/DTOs/CreateUserRequest.cs b/FFOracle/DTOs/CreateUserRequest.cs similarity index 76% rename from Fantasy-Football-Assistant-Manager/DTOs/CreateUserRequest.cs rename to FFOracle/DTOs/CreateUserRequest.cs index 3047240..b6096b6 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/CreateUserRequest.cs +++ b/FFOracle/DTOs/CreateUserRequest.cs @@ -1,4 +1,4 @@ -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class CreateUserRequest { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/Player.cs b/FFOracle/DTOs/Player.cs similarity index 93% rename from Fantasy-Football-Assistant-Manager/DTOs/Player.cs rename to FFOracle/DTOs/Player.cs index 6d970f5..e55fdb0 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/Player.cs +++ b/FFOracle/DTOs/Player.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class Player { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/PlayerStat.cs b/FFOracle/DTOs/PlayerStat.cs similarity index 96% rename from Fantasy-Football-Assistant-Manager/DTOs/PlayerStat.cs rename to FFOracle/DTOs/PlayerStat.cs index 6d7504a..a7ee704 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/PlayerStat.cs +++ b/FFOracle/DTOs/PlayerStat.cs @@ -1,4 +1,4 @@ -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class PlayerStat { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/PlayerStatWithWeekNum.cs b/FFOracle/DTOs/PlayerStatWithWeekNum.cs similarity index 100% rename from Fantasy-Football-Assistant-Manager/DTOs/PlayerStatWithWeekNum.cs rename to FFOracle/DTOs/PlayerStatWithWeekNum.cs diff --git a/Fantasy-Football-Assistant-Manager/DTOs/PlayerWithStats.cs b/FFOracle/DTOs/PlayerWithStats.cs similarity index 100% rename from Fantasy-Football-Assistant-Manager/DTOs/PlayerWithStats.cs rename to FFOracle/DTOs/PlayerWithStats.cs diff --git a/Fantasy-Football-Assistant-Manager/DTOs/RosterSetting.cs b/FFOracle/DTOs/RosterSetting.cs similarity index 92% rename from Fantasy-Football-Assistant-Manager/DTOs/RosterSetting.cs rename to FFOracle/DTOs/RosterSetting.cs index d985072..6ba2cfa 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/RosterSetting.cs +++ b/FFOracle/DTOs/RosterSetting.cs @@ -1,7 +1,6 @@ -using System; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class RosterSetting { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/ScoringSetting.cs b/FFOracle/DTOs/ScoringSetting.cs similarity index 90% rename from Fantasy-Football-Assistant-Manager/DTOs/ScoringSetting.cs rename to FFOracle/DTOs/ScoringSetting.cs index 2c59819..f4c61b9 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/ScoringSetting.cs +++ b/FFOracle/DTOs/ScoringSetting.cs @@ -1,7 +1,6 @@ -using System; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class ScoringSetting { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/Team.cs b/FFOracle/DTOs/Team.cs similarity index 93% rename from Fantasy-Football-Assistant-Manager/DTOs/Team.cs rename to FFOracle/DTOs/Team.cs index b3b3642..3355082 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/Team.cs +++ b/FFOracle/DTOs/Team.cs @@ -1,7 +1,7 @@ using System; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class Team { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/TeamDefensiveStat.cs b/FFOracle/DTOs/TeamDefensiveStat.cs similarity index 95% rename from Fantasy-Football-Assistant-Manager/DTOs/TeamDefensiveStat.cs rename to FFOracle/DTOs/TeamDefensiveStat.cs index 42ddba3..e9bf53a 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/TeamDefensiveStat.cs +++ b/FFOracle/DTOs/TeamDefensiveStat.cs @@ -1,7 +1,7 @@ using System; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class TeamDefensiveStat { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/TeamMember.cs b/FFOracle/DTOs/TeamMember.cs similarity index 86% rename from Fantasy-Football-Assistant-Manager/DTOs/TeamMember.cs rename to FFOracle/DTOs/TeamMember.cs index 1d3adec..bbc6b31 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/TeamMember.cs +++ b/FFOracle/DTOs/TeamMember.cs @@ -1,7 +1,7 @@ using System; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class TeamMember { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/TeamOffensiveStat.cs b/FFOracle/DTOs/TeamOffensiveStat.cs similarity index 96% rename from Fantasy-Football-Assistant-Manager/DTOs/TeamOffensiveStat.cs rename to FFOracle/DTOs/TeamOffensiveStat.cs index ee135e9..8c3fd56 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/TeamOffensiveStat.cs +++ b/FFOracle/DTOs/TeamOffensiveStat.cs @@ -1,7 +1,7 @@ using System; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class TeamOffensiveStat { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/TeamWithStats.cs b/FFOracle/DTOs/TeamWithStats.cs similarity index 100% rename from Fantasy-Football-Assistant-Manager/DTOs/TeamWithStats.cs rename to FFOracle/DTOs/TeamWithStats.cs diff --git a/Fantasy-Football-Assistant-Manager/DTOs/User.cs b/FFOracle/DTOs/User.cs similarity index 92% rename from Fantasy-Football-Assistant-Manager/DTOs/User.cs rename to FFOracle/DTOs/User.cs index 1efeb5d..e3ab7ba 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/User.cs +++ b/FFOracle/DTOs/User.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class User { diff --git a/Fantasy-Football-Assistant-Manager/DTOs/UserTeamMember.cs b/FFOracle/DTOs/UserTeamMember.cs similarity index 100% rename from Fantasy-Football-Assistant-Manager/DTOs/UserTeamMember.cs rename to FFOracle/DTOs/UserTeamMember.cs diff --git a/Fantasy-Football-Assistant-Manager/DTOs/WeeklyPlayerStat.cs b/FFOracle/DTOs/WeeklyPlayerStat.cs similarity index 90% rename from Fantasy-Football-Assistant-Manager/DTOs/WeeklyPlayerStat.cs rename to FFOracle/DTOs/WeeklyPlayerStat.cs index 85e4d77..fabb5c4 100644 --- a/Fantasy-Football-Assistant-Manager/DTOs/WeeklyPlayerStat.cs +++ b/FFOracle/DTOs/WeeklyPlayerStat.cs @@ -1,7 +1,7 @@ using System; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.DTOs; +namespace FFOracle.DTOs; public class WeeklyPlayerStat { diff --git a/Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.csproj b/FFOracle/FFOracle.csproj similarity index 84% rename from Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.csproj rename to FFOracle/FFOracle.csproj index 1836303..aba93e2 100644 --- a/Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.csproj +++ b/FFOracle/FFOracle.csproj @@ -1,10 +1,10 @@ - + net8.0 enable enable - Fantasy_Football_Assistant_Manager + FFOracle diff --git a/Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.sln b/FFOracle/FFOracle.sln similarity index 80% rename from Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.sln rename to FFOracle/FFOracle.sln index d97eb82..87b2551 100644 --- a/Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.sln +++ b/FFOracle/FFOracle.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.14.36414.22 d17.14 +VisualStudioVersion = 17.14.36414.22 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fantasy-Football-Assistant-Manager", "Fantasy-Football-Assistant-Manager.csproj", "{09D8563A-CBBB-4BEC-A6D6-2B1CF117E91D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFOracle", "FFOracle.csproj", "{09D8563A-CBBB-4BEC-A6D6-2B1CF117E91D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Fantasy-Football-Assistant-Manager/Mappings/GameThisWeekCsvMap.cs b/FFOracle/Mappings/GameThisWeekCsvMap.cs similarity index 94% rename from Fantasy-Football-Assistant-Manager/Mappings/GameThisWeekCsvMap.cs rename to FFOracle/Mappings/GameThisWeekCsvMap.cs index 3bd5b1b..56d8152 100644 --- a/Fantasy-Football-Assistant-Manager/Mappings/GameThisWeekCsvMap.cs +++ b/FFOracle/Mappings/GameThisWeekCsvMap.cs @@ -1,8 +1,8 @@ using CsvHelper.Configuration; -using Fantasy_Football_Assistant_Manager.Models.Csv; +using FFOracle.Models.Csv; using System.Globalization; -namespace Fantasy_Football_Assistant_Manager.Mappings; +namespace FFOracle.Mappings; public class GameThisWeekCsvMap: ClassMap { diff --git a/Fantasy-Football-Assistant-Manager/Mappings/PlayerInformationCsvMap.cs b/FFOracle/Mappings/PlayerInformationCsvMap.cs similarity index 79% rename from Fantasy-Football-Assistant-Manager/Mappings/PlayerInformationCsvMap.cs rename to FFOracle/Mappings/PlayerInformationCsvMap.cs index 2c558db..62c0c3e 100644 --- a/Fantasy-Football-Assistant-Manager/Mappings/PlayerInformationCsvMap.cs +++ b/FFOracle/Mappings/PlayerInformationCsvMap.cs @@ -1,7 +1,7 @@ using CsvHelper.Configuration; -using Fantasy_Football_Assistant_Manager.Models.Csv; +using FFOracle.Models.Csv; -namespace Fantasy_Football_Assistant_Manager.Mappings; +namespace FFOracle.Mappings; public class PlayerInformationCsvMap : ClassMap { diff --git a/Fantasy-Football-Assistant-Manager/Mappings/PlayerMap.cs b/FFOracle/Mappings/PlayerMap.cs similarity index 73% rename from Fantasy-Football-Assistant-Manager/Mappings/PlayerMap.cs rename to FFOracle/Mappings/PlayerMap.cs index 8ccccb2..6ba1ebb 100644 --- a/Fantasy-Football-Assistant-Manager/Mappings/PlayerMap.cs +++ b/FFOracle/Mappings/PlayerMap.cs @@ -1,7 +1,7 @@ using CsvHelper.Configuration; -using Fantasy_Football_Assistant_Manager.Models.Supabase; +using FFOracle.Models.Supabase; -namespace Fantasy_Football_Assistant_Manager.Mappings; +namespace FFOracle.Mappings; public class PlayerMap: ClassMap { diff --git a/Fantasy-Football-Assistant-Manager/Mappings/PlayerStatCsvMap.cs b/FFOracle/Mappings/PlayerStatCsvMap.cs similarity index 94% rename from Fantasy-Football-Assistant-Manager/Mappings/PlayerStatCsvMap.cs rename to FFOracle/Mappings/PlayerStatCsvMap.cs index 183dbf3..2c0b85a 100644 --- a/Fantasy-Football-Assistant-Manager/Mappings/PlayerStatCsvMap.cs +++ b/FFOracle/Mappings/PlayerStatCsvMap.cs @@ -1,7 +1,7 @@ using CsvHelper.Configuration; -using Fantasy_Football_Assistant_Manager.Models.Csv; +using FFOracle.Models.Csv; -namespace Fantasy_Football_Assistant_Manager.Mappings; +namespace FFOracle.Mappings; public class PlayerStatCsvMap: ClassMap { diff --git a/Fantasy-Football-Assistant-Manager/Mappings/TeamDataCsvMap.cs b/FFOracle/Mappings/TeamDataCsvMap.cs similarity index 77% rename from Fantasy-Football-Assistant-Manager/Mappings/TeamDataCsvMap.cs rename to FFOracle/Mappings/TeamDataCsvMap.cs index 5a95746..813b4a6 100644 --- a/Fantasy-Football-Assistant-Manager/Mappings/TeamDataCsvMap.cs +++ b/FFOracle/Mappings/TeamDataCsvMap.cs @@ -1,7 +1,7 @@ using CsvHelper.Configuration; -using Fantasy_Football_Assistant_Manager.Models.Csv; +using FFOracle.Models.Csv; -namespace Fantasy_Football_Assistant_Manager.Mappings; +namespace FFOracle.Mappings; public class TeamDataCsvMap: ClassMap { diff --git a/Fantasy-Football-Assistant-Manager/Mappings/TeamSeasonStatCsvMap.cs b/FFOracle/Mappings/TeamSeasonStatCsvMap.cs similarity index 94% rename from Fantasy-Football-Assistant-Manager/Mappings/TeamSeasonStatCsvMap.cs rename to FFOracle/Mappings/TeamSeasonStatCsvMap.cs index e9dd690..7e56fab 100644 --- a/Fantasy-Football-Assistant-Manager/Mappings/TeamSeasonStatCsvMap.cs +++ b/FFOracle/Mappings/TeamSeasonStatCsvMap.cs @@ -1,7 +1,7 @@ using CsvHelper.Configuration; -using Fantasy_Football_Assistant_Manager.Models.Csv; +using FFOracle.Models.Csv; -namespace Fantasy_Football_Assistant_Manager.Mappings; +namespace FFOracle.Mappings; public class TeamSeasonStatCsvMap: ClassMap { diff --git a/Fantasy-Football-Assistant-Manager/Models/Csv/GameThisWeekCsv.cs b/FFOracle/Models/Csv/GameThisWeekCsv.cs similarity index 93% rename from Fantasy-Football-Assistant-Manager/Models/Csv/GameThisWeekCsv.cs rename to FFOracle/Models/Csv/GameThisWeekCsv.cs index b446fa3..1cd02af 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Csv/GameThisWeekCsv.cs +++ b/FFOracle/Models/Csv/GameThisWeekCsv.cs @@ -1,4 +1,4 @@ -namespace Fantasy_Football_Assistant_Manager.Models.Csv; +namespace FFOracle.Models.Csv; public class GameThisWeekCsv { diff --git a/Fantasy-Football-Assistant-Manager/Models/Csv/PlayerInformationCsv.cs b/FFOracle/Models/Csv/PlayerInformationCsv.cs similarity index 83% rename from Fantasy-Football-Assistant-Manager/Models/Csv/PlayerInformationCsv.cs rename to FFOracle/Models/Csv/PlayerInformationCsv.cs index 904d43f..5b8e9fa 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Csv/PlayerInformationCsv.cs +++ b/FFOracle/Models/Csv/PlayerInformationCsv.cs @@ -1,4 +1,4 @@ -namespace Fantasy_Football_Assistant_Manager.Models.Csv; +namespace FFOracle.Models.Csv; public class PlayerInformationCsv { diff --git a/Fantasy-Football-Assistant-Manager/Models/Csv/PlayerStatCsv.cs b/FFOracle/Models/Csv/PlayerStatCsv.cs similarity index 96% rename from Fantasy-Football-Assistant-Manager/Models/Csv/PlayerStatCsv.cs rename to FFOracle/Models/Csv/PlayerStatCsv.cs index 1f76b7b..c87773c 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Csv/PlayerStatCsv.cs +++ b/FFOracle/Models/Csv/PlayerStatCsv.cs @@ -1,4 +1,4 @@ -namespace Fantasy_Football_Assistant_Manager.Models.Csv; +namespace FFOracle.Models.Csv; // class used for parsing the CSV public class PlayerStatCsv diff --git a/Fantasy-Football-Assistant-Manager/Models/Csv/TeamDataCsv.cs b/FFOracle/Models/Csv/TeamDataCsv.cs similarity index 83% rename from Fantasy-Football-Assistant-Manager/Models/Csv/TeamDataCsv.cs rename to FFOracle/Models/Csv/TeamDataCsv.cs index dec244d..5399b23 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Csv/TeamDataCsv.cs +++ b/FFOracle/Models/Csv/TeamDataCsv.cs @@ -1,4 +1,4 @@ -namespace Fantasy_Football_Assistant_Manager.Models.Csv; +namespace FFOracle.Models.Csv; public class TeamDataCsv { diff --git a/Fantasy-Football-Assistant-Manager/Models/Csv/TeamSeasonStatCsv.cs b/FFOracle/Models/Csv/TeamSeasonStatCsv.cs similarity index 95% rename from Fantasy-Football-Assistant-Manager/Models/Csv/TeamSeasonStatCsv.cs rename to FFOracle/Models/Csv/TeamSeasonStatCsv.cs index b0a1430..b7aa80f 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Csv/TeamSeasonStatCsv.cs +++ b/FFOracle/Models/Csv/TeamSeasonStatCsv.cs @@ -1,4 +1,4 @@ -namespace Fantasy_Football_Assistant_Manager.Models.Csv; +namespace FFOracle.Models.Csv; public class TeamSeasonStatCsv { diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/AppState.cs b/FFOracle/Models/Supabase/AppState.cs similarity index 91% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/AppState.cs rename to FFOracle/Models/Supabase/AppState.cs index 81d7e39..5e75c29 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/AppState.cs +++ b/FFOracle/Models/Supabase/AppState.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; [Table("app_state")] public class AppState: BaseModel diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/GameThisWeek.cs b/FFOracle/Models/Supabase/GameThisWeek.cs similarity index 97% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/GameThisWeek.cs rename to FFOracle/Models/Supabase/GameThisWeek.cs index 287ddd5..644f956 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/GameThisWeek.cs +++ b/FFOracle/Models/Supabase/GameThisWeek.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; [Table("games_this_week")] diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/Player.cs b/FFOracle/Models/Supabase/Player.cs similarity index 94% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/Player.cs rename to FFOracle/Models/Supabase/Player.cs index 17067c7..dea30b0 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/Player.cs +++ b/FFOracle/Models/Supabase/Player.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; [Table("players")] public class Player: BaseModel diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/PlayerStat.cs b/FFOracle/Models/Supabase/PlayerStat.cs similarity index 98% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/PlayerStat.cs rename to FFOracle/Models/Supabase/PlayerStat.cs index ad97adf..21f556f 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/PlayerStat.cs +++ b/FFOracle/Models/Supabase/PlayerStat.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; [Table("player_stats")] public class PlayerStat : BaseModel diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/RosterSetting.cs b/FFOracle/Models/Supabase/RosterSetting.cs similarity index 95% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/RosterSetting.cs rename to FFOracle/Models/Supabase/RosterSetting.cs index ba9321f..934a82a 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/RosterSetting.cs +++ b/FFOracle/Models/Supabase/RosterSetting.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; [Table("roster_settings")] diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/ScoringSetting.cs b/FFOracle/Models/Supabase/ScoringSetting.cs similarity index 94% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/ScoringSetting.cs rename to FFOracle/Models/Supabase/ScoringSetting.cs index 3aa66cb..5ea24ff 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/ScoringSetting.cs +++ b/FFOracle/Models/Supabase/ScoringSetting.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; // model for league scoring settings [Table("scoring_settings")] diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/Team.cs b/FFOracle/Models/Supabase/Team.cs similarity index 94% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/Team.cs rename to FFOracle/Models/Supabase/Team.cs index 78021f5..9e1a32a 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/Team.cs +++ b/FFOracle/Models/Supabase/Team.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; [Table("teams")] public class Team : BaseModel diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/TeamDefensiveStat.cs b/FFOracle/Models/Supabase/TeamDefensiveStat.cs similarity index 96% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/TeamDefensiveStat.cs rename to FFOracle/Models/Supabase/TeamDefensiveStat.cs index d6f1c53..ad3baae 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/TeamDefensiveStat.cs +++ b/FFOracle/Models/Supabase/TeamDefensiveStat.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; // used to store a team's defensive stats [Table("team_defensive_stats")] diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/TeamMember.cs b/FFOracle/Models/Supabase/TeamMember.cs similarity index 90% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/TeamMember.cs rename to FFOracle/Models/Supabase/TeamMember.cs index 5699438..df72858 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/TeamMember.cs +++ b/FFOracle/Models/Supabase/TeamMember.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; // relationship table mapping players to user's rosters [Table("team_members")] diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/TeamOffensiveStat.cs b/FFOracle/Models/Supabase/TeamOffensiveStat.cs similarity index 96% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/TeamOffensiveStat.cs rename to FFOracle/Models/Supabase/TeamOffensiveStat.cs index bd66679..b1f0584 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/TeamOffensiveStat.cs +++ b/FFOracle/Models/Supabase/TeamOffensiveStat.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; // stats used when finding DEF to start by matchup [Table("team_offensive_stats")] diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/User.cs b/FFOracle/Models/Supabase/User.cs similarity index 93% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/User.cs rename to FFOracle/Models/Supabase/User.cs index ee17ec0..9464b69 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/User.cs +++ b/FFOracle/Models/Supabase/User.cs @@ -2,7 +2,7 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; [Table("users")] public class User: BaseModel diff --git a/Fantasy-Football-Assistant-Manager/Models/Supabase/WeeklyPlayerStat.cs b/FFOracle/Models/Supabase/WeeklyPlayerStat.cs similarity index 86% rename from Fantasy-Football-Assistant-Manager/Models/Supabase/WeeklyPlayerStat.cs rename to FFOracle/Models/Supabase/WeeklyPlayerStat.cs index 661d6fd..77a4f1a 100644 --- a/Fantasy-Football-Assistant-Manager/Models/Supabase/WeeklyPlayerStat.cs +++ b/FFOracle/Models/Supabase/WeeklyPlayerStat.cs @@ -2,9 +2,9 @@ using Supabase.Postgrest.Attributes; using System.Text.Json.Serialization; -namespace Fantasy_Football_Assistant_Manager.Models.Supabase; +namespace FFOracle.Models.Supabase; -// stores a player's stats for a single week +// stores a player's stats for a single week [Table("weekly_player_stats")] public class WeeklyPlayerStat: BaseModel { diff --git a/Fantasy-Football-Assistant-Manager/Program.cs b/FFOracle/Program.cs similarity index 97% rename from Fantasy-Football-Assistant-Manager/Program.cs rename to FFOracle/Program.cs index 93cd2ae..0cadc05 100644 --- a/Fantasy-Football-Assistant-Manager/Program.cs +++ b/FFOracle/Program.cs @@ -1,4 +1,4 @@ -using Fantasy_Football_Assistant_Manager.Services; +using FFOracle.Services; using Supabase; //using Supabase.Postgrest.Models; diff --git a/Fantasy-Football-Assistant-Manager/Properties/PublishProfiles/fforacle - Web Deploy.pubxml b/FFOracle/Properties/PublishProfiles/fforacle - Web Deploy.pubxml similarity index 100% rename from Fantasy-Football-Assistant-Manager/Properties/PublishProfiles/fforacle - Web Deploy.pubxml rename to FFOracle/Properties/PublishProfiles/fforacle - Web Deploy.pubxml diff --git a/Fantasy-Football-Assistant-Manager/Properties/ServiceDependencies/fforacle - Web Deploy/profile.arm.json b/FFOracle/Properties/ServiceDependencies/fforacle - Web Deploy/profile.arm.json similarity index 100% rename from Fantasy-Football-Assistant-Manager/Properties/ServiceDependencies/fforacle - Web Deploy/profile.arm.json rename to FFOracle/Properties/ServiceDependencies/fforacle - Web Deploy/profile.arm.json diff --git a/Fantasy-Football-Assistant-Manager/Properties/launchSettings.json b/FFOracle/Properties/launchSettings.json similarity index 100% rename from Fantasy-Football-Assistant-Manager/Properties/launchSettings.json rename to FFOracle/Properties/launchSettings.json diff --git a/Fantasy-Football-Assistant-Manager/Services/ChatGPTService.cs b/FFOracle/Services/ChatGPTService.cs similarity index 93% rename from Fantasy-Football-Assistant-Manager/Services/ChatGPTService.cs rename to FFOracle/Services/ChatGPTService.cs index eb78490..24d1a9b 100644 --- a/Fantasy-Football-Assistant-Manager/Services/ChatGPTService.cs +++ b/FFOracle/Services/ChatGPTService.cs @@ -1,6 +1,6 @@ using OpenAI.Chat; -namespace Fantasy_Football_Assistant_Manager.Services; +namespace FFOracle.Services; public class ChatGPTService { diff --git a/Fantasy-Football-Assistant-Manager/Services/NflVerseService.cs b/FFOracle/Services/NflVerseService.cs similarity index 96% rename from Fantasy-Football-Assistant-Manager/Services/NflVerseService.cs rename to FFOracle/Services/NflVerseService.cs index 34e88b2..a3ec41e 100644 --- a/Fantasy-Football-Assistant-Manager/Services/NflVerseService.cs +++ b/FFOracle/Services/NflVerseService.cs @@ -1,12 +1,12 @@ using CsvHelper; using CsvHelper.Configuration; -using Fantasy_Football_Assistant_Manager.Mappings; -using Fantasy_Football_Assistant_Manager.Models.Csv; -using Fantasy_Football_Assistant_Manager.Models.Supabase; +using FFOracle.Mappings; +using FFOracle.Models.Csv; +using FFOracle.Models.Supabase; using System.Globalization; using System.IO.Compression; -namespace Fantasy_Football_Assistant_Manager.Services; +namespace FFOracle.Services; public class NflVerseService { diff --git a/FFOracle/Services/UpdateSupabaseService.cs b/FFOracle/Services/UpdateSupabaseService.cs new file mode 100644 index 0000000..c0fa9ff --- /dev/null +++ b/FFOracle/Services/UpdateSupabaseService.cs @@ -0,0 +1,411 @@ +using FFOracle.Models.Supabase; +using FFOracle.Utils; +using Supabase; + +namespace FFOracle.Services; + +public class UpdateSupabaseService +{ + private readonly NflVerseService _nflVerseService; + private readonly Client _supabase; + + public UpdateSupabaseService(NflVerseService nflVerseService, Client supabase) + { + _nflVerseService = nflVerseService; + _supabase = supabase; + } + + // insert the last three weeks + public async Task<(int startWeek, int endWeek)> UpdateStatsFromLastThreeWeeksAsync() + { + // get all weekly stats + var weeklyStats = await _nflVerseService.GetAllOffensivePlayerWeeklyStatsAsync(); + if (weeklyStats == null || !weeklyStats.Any()) + { + throw new Exception("No season stats found to insert."); + } + + // Get the most latest week's data stored in the db + var latestWeekResponse = await _supabase + .From() + .Select("week") + .Order("week", Supabase.Postgrest.Constants.Ordering.Descending) + .Limit(1) + .Get(); + + // set the week to 0 if there are none in the db currently + var latestWeek = latestWeekResponse.Models.FirstOrDefault()?.Week ?? 0; + + // find the latest week in new data + int newLatestWeek = weeklyStats.Max(s => s.Week); + + // only insert the last 3 weeks + var newStats = weeklyStats + .Where(s => s.Week > latestWeek) + .ToList(); + + // add new stats if they exist + if (newStats.Any()) + { + // convert the newStats to PlayerStats model while generating list of WeeklyPlayerStat models + List weeklyPlayerStats = new List(); + + var playerStats = newStats + .Select(p => + { + var id = Guid.NewGuid(); + + // create and insert new WeeklyPlayerStat model + var weeklyStat = new WeeklyPlayerStat + { + Id = Guid.NewGuid(), + PlayerStatsId = id, + PlayerId = p.PlayerId, + Week = p.Week, + SeasonStartYear = p.SeasonStartYear, + }; + + weeklyPlayerStats.Add(weeklyStat); + + return new PlayerStat + { + Id = id, + Completions = ControllerHelpers.NullIfZero(p.Completions), + PassingAttempts = ControllerHelpers.NullIfZero(p.PassingAttempts), + PassingYards = ControllerHelpers.NullIfZero(p.PassingYards), + PassingTds = ControllerHelpers.NullIfZero(p.PassingTds), + InterceptionsAgainst = ControllerHelpers.NullIfZero(p.InterceptionsAgainst), + SacksAgainst = ControllerHelpers.NullIfZero(p.SacksAgainst), + FumblesAgainst = ControllerHelpers.NullIfZero(p.FumblesAgainst), + PassingFirstDowns = ControllerHelpers.NullIfZero(p.PassingFirstDowns), + PassingEpa = ControllerHelpers.NullIfZero(p.PassingEpa), + Carries = ControllerHelpers.NullIfZero(p.Carries), + RushingYards = ControllerHelpers.NullIfZero(p.RushingYards), + RushingTds = ControllerHelpers.NullIfZero(p.RushingTds), + RushingFirstDowns = ControllerHelpers.NullIfZero(p.RushingFirstDowns), + RushingEpa = ControllerHelpers.NullIfZero(p.RushingEpa), + Receptions = ControllerHelpers.NullIfZero(p.Receptions), + Targets = ControllerHelpers.NullIfZero(p.Targets), + ReceivingYards = ControllerHelpers.NullIfZero(p.ReceivingYards), + ReceivingTds = ControllerHelpers.NullIfZero(p.ReceivingTds), + ReceivingFirstDowns = ControllerHelpers.NullIfZero(p.ReceivingFirstDowns), + ReceivingEpa = ControllerHelpers.NullIfZero(p.ReceivingEpa), + FgMadeList = p.FgMadeList, + FgMissedList = p.FgMissedList, + FgBlockedList = p.FgBlockedList, + PadAttempts = ControllerHelpers.NullIfZero(p.PadAttempts), + PatPercent = ControllerHelpers.NullIfZero(p.PatPercent), + FantasyPoints = p.FantasyPoints, + FantasyPointsPpr = p.FantasyPointsPpr + }; + }) + .ToList(); + + // insert PlayerStat models into player_stats table + var insertedStatsResponse = await _supabase + .From() + .Insert(playerStats); + + // insert new relational record into weekly_player_stats table + var insertedWeeklyResponse = await _supabase + .From() + .Insert(weeklyPlayerStats); + } + + // find the range of the last 3 weeks + int startWeek = Math.Max(newLatestWeek - 2, 1); + + // delete all old weekly stats + await _supabase.Rpc("delete_old_week_stats", new { min_week = startWeek }); + + return (startWeek, newLatestWeek); + } + + // update all player's season stats + public async Task UpdateAllPlayerSeasonStatsAsync() + { + // fetch offensive players using service + var seasonStats = await _nflVerseService.GetAllOffensivePlayerSeasonStatsAsync(); + if (seasonStats == null || !seasonStats.Any()) + { + throw new Exception("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() + .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 }); + + return; + } + + // update all player's non stat data + public async Task UpdateAllPlayerNonStatDataAsync() + { + // get all player ids from supabase + var response = await _supabase + .From() + .Get(); + if (response.Models == null || !response.Models.Any()) + { + throw new Exception("No players found in database."); + } + + var existingPlayers = response.Models; + var playerLookup = existingPlayers.ToDictionary(p => p.Id, StringComparer.OrdinalIgnoreCase); + + // get the current season and week from supabase + var (currentSeason, currentWeek) = await ControllerHelpers.GetCurrentSeasonAndWeekAsync(_supabase); + + // get all player information from nflverse service + var playerInformation = await _nflVerseService.GetAllPlayerInformationAsync(currentSeason); + + if (playerInformation == null || !playerInformation.Any()) + { + throw new Exception("No player information found."); + } + + // merge info only for players that exist in Supabase + var filteredPlayerWithInfo = playerInformation + .Where(info => !string.IsNullOrWhiteSpace(info.Id) && playerLookup.ContainsKey(info.Id)) + .Select(info => + { + var existingPlayer = playerLookup[info.Id]; + return new Player + { + Id = existingPlayer.Id, + Name = existingPlayer.Name, + HeadshotUrl = existingPlayer.HeadshotUrl, + Position = existingPlayer.Position, + Status = info.Status, + StatusDescription = info.ShortDescription, + TeamId = info.LatestTeam, + SeasonStatsId = existingPlayer.SeasonStatsId + }; + }) + .ToList(); + + if (!filteredPlayerWithInfo.Any()) + { + throw new Exception("No matching players to update."); + } + + // update Supabase players table + var updateResponse = await _supabase + .From() + .OnConflict(x => x.Id) + .Upsert(filteredPlayerWithInfo); + + return; + } + + // update team offensive stats + public async Task UpdateAllTeamSeasonStatsAsync() + { + // fetch team stats using service + var stats = await _nflVerseService.GetAllTeamSeasonStatsAsync(); + if (stats == null || !stats.Any()) + { + throw new Exception("No team stats found to insert."); + } + + // break up the stats into offensive and defensive stats, keeping track of the new ids + List offensiveStats = new List(); + List defensiveStats = new List(); + Dictionary teamOffensiveStatIdMap = new Dictionary(); + Dictionary teamDefensiveStatIdMap = new Dictionary(); + + + foreach (var stat in stats) + { + if (stat == null) continue; + + // add the ids to the corresponding maps + Guid offensiveId = Guid.NewGuid(); + Guid defensiveId = Guid.NewGuid(); + teamOffensiveStatIdMap[stat.Team] = offensiveId; + teamDefensiveStatIdMap[stat.Team] = defensiveId; + + // parse out the offensive stats and add to list + var offensiveStat = new TeamOffensiveStat + { + Id = offensiveId, + Completions = ControllerHelpers.NullIfZero(stat.Completions), + Attempts = ControllerHelpers.NullIfZero(stat.Attempts), + PassingYards = ControllerHelpers.NullIfZero(stat.PassingYards), + PassingTds = ControllerHelpers.NullIfZero(stat.PassingTds), + PassingInterceptions = ControllerHelpers.NullIfZero(stat.PassingInterceptions), + SacksAgainst = ControllerHelpers.NullIfZero(stat.SacksAgainst), + FumblesAgainst = ControllerHelpers.NullIfZero(stat.FumblesAgainst), + Carries = ControllerHelpers.NullIfZero(stat.Carries), + RushingYards = ControllerHelpers.NullIfZero(stat.RushingYards), + RushingTds = ControllerHelpers.NullIfZero(stat.RushingTds), + Receptions = ControllerHelpers.NullIfZero(stat.Receptions), + Targets = ControllerHelpers.NullIfZero(stat.Targets), + ReceivingYards = ControllerHelpers.NullIfZero(stat.ReceivingYards), + ReceivingTds = ControllerHelpers.NullIfZero(stat.ReceivingTds) + }; + + offensiveStats.Add(offensiveStat); + + // parse out the defensive stats and add to list + var defensiveStat = new TeamDefensiveStat + { + Id = defensiveId, + TacklesForLoss = ControllerHelpers.NullIfZero(stat.TacklesForLoss), + TacklesForLossYards = ControllerHelpers.NullIfZero(stat.TacklesForLossYards), + FumblesFor = ControllerHelpers.NullIfZero(stat.FumblesFor), + SacksFor = ControllerHelpers.NullIfZero(stat.SacksFor), + SackYardsFor = ControllerHelpers.NullIfZero(stat.SackYardsFor), + InterceptionsFor = ControllerHelpers.NullIfZero(stat.InterceptionsFor), + InterceptionYardsFor = ControllerHelpers.NullIfZero(stat.InterceptionYardsFor), + DefTds = ControllerHelpers.NullIfZero(stat.DefTds), + Safeties = ControllerHelpers.NullIfZero(stat.Safeties), + PassDefended = ControllerHelpers.NullIfZero(stat.PassDefended) + }; + + defensiveStats.Add(defensiveStat); + } + + // get existing teams from supabase + var teamsResponse = await _supabase + .From() + .Get(); + + if (teamsResponse.Models == null || !teamsResponse.Models.Any()) + { + throw new Exception("No teams found in the database"); + } + + var existingTeams = teamsResponse.Models; + + // create updated teams list + var updatedTeams = existingTeams + .Where(t => teamOffensiveStatIdMap.ContainsKey(t.Id)) + .Select(t => + new Team + { + Id = t.Id, + Name = t.Name, + Conference = t.Conference, + Division = t.Division, + LogoUrl = t.LogoUrl, + OffensiveStatsId = teamOffensiveStatIdMap[t.Id], + DefensiveStatsId = teamDefensiveStatIdMap[t.Id] + } + ) + .ToList(); + + // insert the offensive stats + await _supabase + .From() + .Insert(offensiveStats); + + // insert the defensive stats + await _supabase + .From() + .Insert(defensiveStats); + + // update the team records to reference the stats + await _supabase + .From() + .OnConflict(x => x.Id) + .Upsert(updatedTeams); + + return; + } + + // update games this week + public async Task UpdateGamesThisWeekAsync() + { + // 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()) + { + throw new Exception("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() + .Where(g => g.Id != null) + .Delete(); + + await _supabase + .From() + .Insert(gamesToInsert); + + return; + } +} diff --git a/Fantasy-Football-Assistant-Manager/Utils/ControllerHelpers.cs b/FFOracle/Utils/ControllerHelpers.cs similarity index 84% rename from Fantasy-Football-Assistant-Manager/Utils/ControllerHelpers.cs rename to FFOracle/Utils/ControllerHelpers.cs index 36de2b2..e523c17 100644 --- a/Fantasy-Football-Assistant-Manager/Utils/ControllerHelpers.cs +++ b/FFOracle/Utils/ControllerHelpers.cs @@ -1,8 +1,8 @@ -using Fantasy_Football_Assistant_Manager.Models; -using Fantasy_Football_Assistant_Manager.Models.Supabase; +using FFOracle.Models; +using FFOracle.Models.Supabase; using Supabase; -namespace Fantasy_Football_Assistant_Manager.Utils; +namespace FFOracle.Utils; public class ControllerHelpers { diff --git a/Fantasy-Football-Assistant-Manager/Controllers/PlayerWeeklyStatController.cs b/Fantasy-Football-Assistant-Manager/Controllers/PlayerWeeklyStatController.cs deleted file mode 100644 index 125219c..0000000 --- a/Fantasy-Football-Assistant-Manager/Controllers/PlayerWeeklyStatController.cs +++ /dev/null @@ -1,184 +0,0 @@ -using Fantasy_Football_Assistant_Manager.Models.Supabase; -using Fantasy_Football_Assistant_Manager.Services; -using Fantasy_Football_Assistant_Manager.Utils; -using Microsoft.AspNetCore.Mvc; -using Supabase; - -namespace Fantasy_Football_Assistant_Manager.Controllers; - -[ApiController] -[Route("api/[controller]")] -public class PlayerWeeklyStatController : ControllerBase -{ - private readonly NflVerseService _nflVerseService; - private readonly Client _supabase; - - // injects NflVerseService via dependency injection - public PlayerWeeklyStatController(NflVerseService nflVerseService, Client supabase) - { - _nflVerseService = nflVerseService; - _supabase = 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 PostAll() - { - try - { - var weeklyStats = await _nflVerseService.GetAllOffensivePlayerWeeklyStatsAsync(); - - if (weeklyStats == null || !weeklyStats.Any()) - { - return BadRequest("No season stats found to insert."); - } - - // Get the most latest week's data stored in the db - var latestWeekResponse = await _supabase - .From() - .Select("week") - .Order("week", Supabase.Postgrest.Constants.Ordering.Descending) - .Limit(1) - .Get(); - - // set the week to 0 if there are none in the db currently - var latestWeek = latestWeekResponse.Models.FirstOrDefault()?.Week ?? 0; - - // find the latest week in new data - int newLatestWeek = weeklyStats.Max(s => s.Week); - - // only insert the weeks AFTER the last recorded one - var newStats = weeklyStats - .Where(s => s.Week > latestWeek) - .ToList(); - - if (!newStats.Any()) - { - return Ok(new { message = $"No new weekly stats to insert (latest week: {latestWeek})" }); - } - - // convert the newStats to PlayerStats model while generating list of WeeklyPlayerStat models - List weeklyPlayerStats = new List(); - - var playerStats = newStats - .Select(p => - { - var id = Guid.NewGuid(); - - // create and insert new WeeklyPlayerStat model - var weeklyStat = new WeeklyPlayerStat - { - Id = Guid.NewGuid(), - PlayerStatsId = id, - PlayerId = p.PlayerId, - Week = p.Week, - SeasonStartYear = p.SeasonStartYear, - }; - - weeklyPlayerStats.Add(weeklyStat); - - return new PlayerStat - { - Id = id, - Completions = ControllerHelpers.NullIfZero(p.Completions), - PassingAttempts = ControllerHelpers.NullIfZero(p.PassingAttempts), - PassingYards = ControllerHelpers.NullIfZero(p.PassingYards), - PassingTds = ControllerHelpers.NullIfZero(p.PassingTds), - InterceptionsAgainst = ControllerHelpers.NullIfZero(p.InterceptionsAgainst), - SacksAgainst = ControllerHelpers.NullIfZero(p.SacksAgainst), - FumblesAgainst = ControllerHelpers.NullIfZero(p.FumblesAgainst), - PassingFirstDowns = ControllerHelpers.NullIfZero(p.PassingFirstDowns), - PassingEpa = ControllerHelpers.NullIfZero(p.PassingEpa), - Carries = ControllerHelpers.NullIfZero(p.Carries), - RushingYards = ControllerHelpers.NullIfZero(p.RushingYards), - RushingTds = ControllerHelpers.NullIfZero(p.RushingTds), - RushingFirstDowns = ControllerHelpers.NullIfZero(p.RushingFirstDowns), - RushingEpa = ControllerHelpers.NullIfZero(p.RushingEpa), - Receptions = ControllerHelpers.NullIfZero(p.Receptions), - Targets = ControllerHelpers.NullIfZero(p.Targets), - ReceivingYards = ControllerHelpers.NullIfZero(p.ReceivingYards), - ReceivingTds = ControllerHelpers.NullIfZero(p.ReceivingTds), - ReceivingFirstDowns = ControllerHelpers.NullIfZero(p.ReceivingFirstDowns), - ReceivingEpa = ControllerHelpers.NullIfZero(p.ReceivingEpa), - FgMadeList = p.FgMadeList, - FgMissedList = p.FgMissedList, - FgBlockedList = p.FgBlockedList, - PadAttempts = ControllerHelpers.NullIfZero(p.PadAttempts), - PatPercent = ControllerHelpers.NullIfZero(p.PatPercent), - FantasyPoints = p.FantasyPoints, - FantasyPointsPpr = p.FantasyPointsPpr - }; - }) - .ToList(); - - // insert PlayerStat models into player_stats table - var insertedStatsResponse = await _supabase - .From() - .Insert(playerStats); - - // insert new relational record into weekly_player_stats table - var insertedWeeklyResponse = await _supabase - .From() - .Insert(weeklyPlayerStats); - - return Ok(new { message = $"Inserted weekly stats successfully for weeks {latestWeek + 1}–{newStats.Max(s => s.Week)}" }); - } - 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 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() - .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() - .Filter("id", Supabase.Postgrest.Constants.Operator.In, batchIds) - .Delete(); - - // delete all batch records from weekly_player_stats table - await _supabase - .From() - .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}"); - } - } -} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/StripeController.cs b/Fantasy-Football-Assistant-Manager/Controllers/StripeController.cs deleted file mode 100644 index 1266f80..0000000 --- a/Fantasy-Football-Assistant-Manager/Controllers/StripeController.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Stripe; - -namespace Fantasy_Football_Assistant_Manager.Controllers -{ - - [ApiController] //establishes that class will handle API requests - [Route("api/[controller]")] //establishes this controller's base URL path - public class StripeController : Controller //Controller class gives access to objects/methods needed to handle HTTP - { - private readonly IConfiguration _config; //accesses appsettings information, including keys - - //constructor - public StripeController(IConfiguration config) - { - _config = config; - } - - //endpoint for creating a payment intent - //The [FromBody] tag tells the code that this method will receive data from a passed in JSON file. - // The file contents are then automatically mapped to the attributes of the argument type. - [HttpPost("create-payment-intent")] //establishes that post requests will come in at this URL - public async Task CreatePaymentIntent([FromBody] PaymentRequest request) - { - //The payment intent is an object that is carried across the entire payment process. - //It holds information about a specific payment and tracks the status of that payment. - - StripeConfiguration.ApiKey = _config["Stripe:SecretKey"]; //provide API key - - //Collect information on the new payment - var options = new PaymentIntentCreateOptions - { - Amount = request.Amount, // amount to pay; for USD, it is in cents - Currency = "usd", //currency denomination - PaymentMethodTypes = new List { "card" }, //payment types to offer - }; - - var service = new PaymentIntentService(); //PaymentIntentService is a class for interfacing with the API - var paymentIntent = await service.CreateAsync(options); //request a new payment intent from the Stripe servers - - //return a success response. Do not pass the payment intent to the client, just sent the client secret. - //The client secret is a frontend-safe identifier for a transaction/payment intent. - return Ok(new { clientSecret = paymentIntent.ClientSecret }); - } - } - - //class to contain payment quantity information. Populated by JSON sent from client and passed - // to controller as input. - public class PaymentRequest - { - public long Amount { get; set; } - } -} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/StripeWebhookController.cs b/Fantasy-Football-Assistant-Manager/Controllers/StripeWebhookController.cs deleted file mode 100644 index 0606f8e..0000000 --- a/Fantasy-Football-Assistant-Manager/Controllers/StripeWebhookController.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Stripe; -using static Microsoft.IO.RecyclableMemoryStreamManager; - -namespace Fantasy_Football_Assistant_Manager.Controllers -{ - [ApiController] - [Route("api/[controller]")] - public class StripeWebhookController : Controller - { - - private readonly ILogger _logger; //a logger I'll use to indicate successful payments - private readonly IConfiguration _config; //accesses appsettings info - - //constructor to initialize the stored logger and config - public StripeWebhookController(ILogger logger, IConfiguration config) - { - _logger = logger; - _config = config; - } - - [HttpPost("check-payment-completion")] - public async Task HandleStripeWebhook() - { - //Read the request body - var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); - - //retrieve the webhook secret. - //As of now, WE HAVE NO WEBHOOK ENDPOINT WITH STRIPE. That requires a proper public URL for our - // backend. I have used the stripe CLI to make a temporary bridge from which we can get responses. - var webhookSecret = _config["Stripe:WebhookSecret"]; - //verify that it's actually there - if (string.IsNullOrEmpty(webhookSecret)) - { - _logger.LogError("Stripe WebhookSecret is not configured."); - return BadRequest("Webhook secret not configured."); - } - - try - { - //Parse the message into an Event object and ensure it's a valid stripe message - var stripeEvent = EventUtility.ConstructEvent( - json, - Request.Headers["Stripe-Signature"], //use this to verify that it has the stripe header - webhookSecret - ); - - //Each case of the switch statement handles a different event type. - switch (stripeEvent.Type) - { - case "payment_intent.succeeded": - { - //for now, just log the success - var paymentIntent = stripeEvent.Data.Object as PaymentIntent; - _logger.LogInformation("PaymentIntent succeeded: {Id}, amount: {Amount}", paymentIntent.Id, paymentIntent.Amount); - //This is where DB would be updated - break; - } - - case "payment_intent.payment_failed": - { - //for now, just log the failure - var paymentIntent = stripeEvent.Data.Object as PaymentIntent; - _logger.LogWarning("PaymentIntent failed: {Id}", paymentIntent.Id); - //This is where a user notification might be called - break; - } - //I can add other events here as needed, but for now we only need those two - - //Log any event aside from the ones expected - default: - _logger.LogInformation("Unhandled Stripe event type: {Type}", stripeEvent.Type); - break; - } - - //This response tells stripe the message was received - return Ok(); - } - catch (StripeException ex) - { - //If anything goes wrong in validating the message, log it and tell stripe a bad message was received - _logger.LogError(ex, "Stripe webhook handling failed: {Message}", ex.Message); - return BadRequest(); - } - catch (System.Exception ex) - { - //Catch other errors - _logger.LogError(ex, "Unexpected error handling Stripe webhook"); - return StatusCode(500); - } - } - } -} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/GetPlayersByPositionController.cs b/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/GetPlayersByPositionController.cs deleted file mode 100644 index 79190c4..0000000 --- a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/GetPlayersByPositionController.cs +++ /dev/null @@ -1,262 +0,0 @@ -using Fantasy_Football_Assistant_Manager.Models.Supabase; -using Microsoft.AspNetCore.Mvc; -using Supabase; -using Supabase.Postgrest.Responses; -using static Supabase.Postgrest.Constants; - -//A controller to retrieve all data specific to a certain user -//Based on the SupeBaseController code used for our test app - -namespace Fantasy_Football_Assistant_Manager.Controllers.SupaBaseControllers; - -[ApiController] -[Route("api/[controller]")] -public class GetPlayersByPositionController : ControllerBase -{ - private readonly Client _supabase; - - public GetPlayersByPositionController(Client supabase) - { - _supabase = supabase; - } - - // Read-only endpoint to fetch all players of a certain position. - // If the position is defense, it instead returns all teams. - [HttpGet("{position}/{seasonStartYear}")] - public async Task GetPlayersByPosition(String position, int seasonStartYear) - { - try - { - //if the defense has been requested, return a list of teams since defense - // is per-team - if (position.Equals("DEF")) - { - //get each team - var teamsRes = await _supabase.From().Get(); - var teams = teamsRes.Models.ToList(); - //for each team, get its stats and create a new teamWithStats DTO - var teamWithStatsTasks = teams.Select(async team => - { - //get the team stats - var defRes = await _supabase - .From() - .Where(s => s.Id == team.DefensiveStatsId) - .Get(); - var offRes = await _supabase - .From() - .Where(s => s.Id == team.OffensiveStatsId) - .Get(); - var def = defRes.Model; - var off = offRes.Model; - //Create the full DTO - var teamWithStats = new DTOs.TeamWithStats - { - team = new DTOs.Team - { - Id = team.Id, - Name = team.Name, - OffensiveStatsId = team.OffensiveStatsId, - DefensiveStatsId = team.DefensiveStatsId - }, - defStat = new DTOs.TeamDefensiveStat - { - Id = def.Id, - TacklesForLoss = def.TacklesForLoss, - TacklesForLossYards = def.TacklesForLossYards, - FumblesFor = def.FumblesFor, - SacksFor = def.SacksFor, - SackYardsFor = def.SackYardsFor, - InterceptionsFor = def.InterceptionsFor, - InterceptionYardsFor = def.InterceptionYardsFor, - DefTds = def.DefTds, - Safeties = def.Safeties, - PassDefended = def.PassDefended - }, - offStat = new DTOs.TeamOffensiveStat - { - Id = off.Id, - Completions = off.Completions, - Attempts = off.Attempts, - PassingYards = off.PassingYards, - PassingTds = off.PassingTds, - PassingInterceptions = off.PassingInterceptions, - SacksAgainst = off.SacksAgainst, - FumblesAgainst = off.FumblesAgainst, - Carries = off.Carries, - RushingYards = off.RushingYards, - RushingTds = off.RushingTds, - Receptions = off.Receptions, - Targets = off.Targets, - ReceivingYards = off.ReceivingYards, - ReceivingTds = off.ReceivingTds - } - }; - return teamWithStats; - }); - //since the query that forms the list of DTOs contains multiple async calls, - // the program needs to wait for everything to complete before it can - // use the results. - var teamWithStatsDTOs = (await Task.WhenAll(teamWithStatsTasks)).ToList(); - - return Ok(teamWithStatsDTOs); - } - //else, return a list of players of that particular position - else - { - //First, get each player of that position joined to their season stats - //Note about the syntax: the * indicates all fields. - //Since there is a foreign key for stats in players, I can join the two tables - // implicitly like this: - var playersRes = await _supabase - .From() - //.Select("*, season_stats(*)") - .Where(p => p.Position == position) - .Get(); - var players = playersRes.Models; - - //Second, call a helper method to bind the players' stats to the players - var playerWithStatsDTOs = await Helper.GetPlayersWithStats(players, _supabase, seasonStartYear); - - ////Second, retrieve all of the player-to-weeklyStats mappings. - //var playerIds = players.Select(p => p.Id).ToList(); //used to get all mappings associated with pulled players - //var mappingRes = await _supabase - // .From() - // .Where(m => m.SeasonStartYear == seasonStartYear) //ensure this is from the right season - // .Filter(x => x.PlayerId, Operator.In, playerIds) - // .Get(); - //var mappings = mappingRes.Models; - - ////Third, use those mappings to retrieve all of the weekly stats. - //var mappingIds = mappings.Select(m => m.PlayerStatsId).ToList(); //used to get all stats associated with pulled players - //var statsRes = await _supabase - // .From() - // .Filter(x => x.Id, Operator.In, mappingIds) - // .Get(); - //var stats = statsRes.Models; - - ////Fourth, combine each player's weekly stats into a list and use all of the - //// collected info to form the playerWithStats DTOs. - //var playerTasks = players.Select(async player => - //{ - // //get this player's season stats - // var seasonStatsRes = await _supabase - // .From() - // .Where(s => s.Id == player.SeasonStatsId) - // .Get(); - // var seasonStats = seasonStatsRes.Model; - // //get this player's stats mappings, making sure to not select the season stats - // var playerMappings = mappings - // .Where(m => m.PlayerId == player.Id && m.PlayerStatsId != player.SeasonStatsId) - // .ToList(); - // //Use these mappings to get all weekly stats and their associated week number - // var weeklyStatTasks = playerMappings.Select(async m => - // { - // //for each mapping, get the associated stat - // var statRes = await _supabase - // .From() - // .Where(s => s.Id == m.PlayerStatsId) - // .Get(); - // var stat = statRes.Model; - // //from that stat, create a DTO - // var statDTO = new DTOs.PlayerStat - // { - // Completions = stat.Completions, - // PassingAttempts = stat.PassingAttempts, - // PassingYards = stat.PassingYards, - // PassingTds = stat.PassingTds, - // InterceptionsAgainst = stat.InterceptionsAgainst, - // SacksAgainst = stat.SacksAgainst, - // FumblesAgainst = stat.FumblesAgainst, - // PassingFirstDowns = stat.PassingFirstDowns, - // PassingEpa = stat.PassingEpa, - // Carries = stat.Carries, - // RushingYards = stat.RushingYards, - // RushingTds = stat.RushingTds, - // RushingFirstDowns = stat.RushingFirstDowns, - // RushingEpa = stat.RushingEpa, - // Receptions = stat.Receptions, - // Targets = stat.Targets, - // ReceivingYards = stat.ReceivingYards, - // ReceivingTds = stat.ReceivingTds, - // ReceivingFirstDowns = stat.ReceivingFirstDowns, - // ReceivingEpa = stat.ReceivingEpa, - // FgMadeList = stat.FgMadeList, - // FgMissedList = stat.FgMissedList, - // FgBlockedList = stat.FgBlockedList, - // PadAttempts = stat.PadAttempts, - // PatPercent = stat.PatPercent, - // FantasyPoints = stat.FantasyPoints, - // FantasyPointsPpr = stat.FantasyPointsPpr - // }; - // //create a final DTO holding the stat DTO and its week number - // var statWithWeek = new DTOs.PlayerStatWithWeekNum - // { - // Week = m.Week, - // Stat = statDTO - // }; - // return statWithWeek; - // }).ToList(); - // //Since this query contains its own async operations, it can only - // // return tasks. The program needs to wait on those to finish to - // // get the final result before it moves on. - // var weeklyStatDTOs = (await Task.WhenAll(weeklyStatTasks)).ToList(); - - // return new DTOs.PlayerWithStats - // { - // Player = new DTOs.Player - // { - // Id = player.Id, - // Name = player.Name, - // HeadshotUrl = player.HeadshotUrl, - // Position = player.Position, - // Status = player.Status, - // StatusDescription = player.StatusDescription, - // TeamId = player.TeamId, - // SeasonStatsId = player.SeasonStatsId - // }, - // SeasonStat = new DTOs.PlayerStat - // { - // Completions = seasonStats.Completions, - // PassingAttempts = seasonStats.PassingAttempts, - // PassingYards = seasonStats.PassingYards, - // PassingTds = seasonStats.PassingTds, - // InterceptionsAgainst = seasonStats.InterceptionsAgainst, - // SacksAgainst = seasonStats.SacksAgainst, - // FumblesAgainst = seasonStats.FumblesAgainst, - // PassingFirstDowns = seasonStats.PassingFirstDowns, - // PassingEpa = seasonStats.PassingEpa, - // Carries = seasonStats.Carries, - // RushingYards = seasonStats.RushingYards, - // RushingTds = seasonStats.RushingTds, - // RushingFirstDowns = seasonStats.RushingFirstDowns, - // RushingEpa = seasonStats.RushingEpa, - // Receptions = seasonStats.Receptions, - // Targets = seasonStats.Targets, - // ReceivingYards = seasonStats.ReceivingYards, - // ReceivingTds = seasonStats.ReceivingTds, - // ReceivingFirstDowns = seasonStats.ReceivingFirstDowns, - // ReceivingEpa = seasonStats.ReceivingEpa, - // FgMadeList = seasonStats.FgMadeList, - // FgMissedList = seasonStats.FgMissedList, - // FgBlockedList = seasonStats.FgBlockedList, - // PadAttempts = seasonStats.PadAttempts, - // PatPercent = seasonStats.PatPercent, - // FantasyPoints = seasonStats.FantasyPoints, - // FantasyPointsPpr = seasonStats.FantasyPointsPpr - // }, - // WeeklyStats = weeklyStatDTOs - // }; - //}).ToList(); - //Make sure to wait for all tasks to be completed before returning. - //List playerDTOs = (await Task.WhenAll(playerTasks)).ToList(); - - return Ok(playerWithStatsDTOs); - } - } - catch (Exception ex) - { - - return StatusCode(500, new { error = "Error fetching data from Supabase", details = ex.Message }); - } - } -} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/GetUserDataController.cs b/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/GetUserDataController.cs deleted file mode 100644 index c0bca85..0000000 --- a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/GetUserDataController.cs +++ /dev/null @@ -1,136 +0,0 @@ -//using Fantasy_Football_Assistant_Manager.DTOs; -using Fantasy_Football_Assistant_Manager.Models.Supabase; -using Microsoft.AspNetCore.Mvc; -using Supabase; -using Supabase.Postgrest.Responses; -using static Supabase.Postgrest.Constants; - -//A controller to retrieve all data specific to a certain user -//Based on the SupeBaseController code used for our test app - -namespace Fantasy_Football_Assistant_Manager.Controllers.SupaBaseControllers; - -[ApiController] -[Route("api/[controller]")] -public class GetUserDataController : ControllerBase -{ - private readonly Client _supabase; - - public GetUserDataController(Client supabase) - { - _supabase = supabase; - } - - // Read-only endpoint to fetch all user data: account info, settings, team - [HttpGet] - public async Task GetUserData() - { - try - { - // get the token from the Authorization header - var authHeader = Request.Headers["Authorization"].ToString(); - if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ")) - { - return Unauthorized("Missing or invalid token"); - } - var accessToken = authHeader.Substring("Bearer ".Length); - // verify the token with Supabase - var user = await _supabase.Auth.GetUser(accessToken); - if (user == null) - { - return Unauthorized("Invalid token"); - } - var userId = Guid.Parse(user.Id); - if (userId == Guid.Empty) - { - return Unauthorized("Invalid token"); - } - - - // fetch user data from supabase database - var userRes = await _supabase.From().Where(x => x.Id == userId).Get(); - - // map supabase result to dto - var userData = userRes.Model; - - var userDTO = new DTOs.User(); - userDTO.Id = userData.Id; - userDTO.TeamName = userData.TeamName; - userDTO.Email = userData.Email; - userDTO.TokensLeft = userData.TokensLeft; - //no need to send the settings entry IDs since these will be queried separately - - // fetch score settings and map to dto - var scoreRes = await _supabase.From().Where(x => x.Id == userData.ScoringSettingsId).Get(); - var s = scoreRes.Model; - var scoreDTO = new DTOs.ScoringSetting(); - scoreDTO.PointsPerReception = s.PointsPerReception; - scoreDTO.PointsPerReceptionYard = s.PointsPerReceptionYard; - scoreDTO.PointsPerRushingYard = s.PointsPerRushingYard; - scoreDTO.PointsPerPassingYard = s.PointsPerPassingYard; - scoreDTO.PointsPerTd = s.PointsPerTd; - - //fetch roster settings and map to dto - var rosterRes = await _supabase.From().Where(x => x.Id == userData.RosterSettingsId).Get(); - var r = rosterRes.Model; - var rosterDTO = new DTOs.RosterSetting(); - rosterDTO.KCount = r.KCount; - rosterDTO.WrCount = r.WrCount; - rosterDTO.IrCount = r.IrCount; - rosterDTO.QbCount = r.QbCount; - rosterDTO.RbCount = r.RbCount; - rosterDTO.BenchCount = r.BenchCount; - rosterDTO.DefCount = r.DefCount; - rosterDTO.FlexCount = r.FlexCount; - rosterDTO.TeCount = r.TeCount; - - //fetch team member data and map to list of DTOs - //start by fetching the list of TeamMember objects for this user - var membersRes = await _supabase - .From() - .Where(x => x.UserId == userData.Id) - .Get(); - var members = membersRes.Models; - //Extract a list of just the team member IDs and use it to fetch a list of - // player objects for the players on this user's team. - var memberIDs = members - .Select(m => m.PlayerId) - .ToList(); - var playersRes = await _supabase - .From() - .Filter(p => p.Id, Operator.In, memberIDs) - .Get(); - var players = playersRes.Models; - //Use a helper method to obtain a list of players with associated stats - var playerWithStatsDTOs = await Helper.GetPlayersWithStats(players: players, _supabase: _supabase); - - //Combine the picked fields of the members list with the playerWithStats DTOs - var userTeamMemberDTOs = members.Select(m => - { - var utm = new DTOs.UserTeamMember - { - Picked = m.Picked, - Player = playerWithStatsDTOs - .Where(p => p.Player.Id == m.PlayerId.ToString()) - .Single() - }; - return utm; - }).ToList(); - - //return all user data - var result = new - { - userDTO, - scoreDTO, - rosterDTO, - userTeamMemberDTOs - }; - return Ok(result); - } - catch (Exception ex) - { - - return StatusCode(500, new { error = "Error fetching data from Supabase", details = ex.Message }); - } - } -} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/Helper.cs b/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/Helper.cs deleted file mode 100644 index fbd2552..0000000 --- a/Fantasy-Football-Assistant-Manager/Controllers/SupaBaseControllers/Helper.cs +++ /dev/null @@ -1,285 +0,0 @@ -using Fantasy_Football_Assistant_Manager.Models.Supabase; -using Microsoft.AspNetCore.Mvc; -using Supabase; -using Supabase.Postgrest.Responses; -using static Supabase.Postgrest.Constants; - -namespace Fantasy_Football_Assistant_Manager.Controllers.SupaBaseControllers - -{ - //A helper class to hold what would have otherwise been a long stretch of redundant code - public static class Helper - { - //A method to collect and return a list of DTOs holding players with their associated stats - public static async Task> GetPlayersWithStats( - List players, //list of base player objects - Client _supabase, //the supabase client to search - int? seasonStartYear = null //the weekly stats season start year field to check for - ) - { - //First, retrieve all of the player-to-weeklyStats mappings. - var playerIds = players.Select(p => p.Id).ToList(); //used to get all mappings associated with pulled players - //The user may not want to filter by seasonStartYear. Only add that filter - // if an argument was given. - var mappingQuery = _supabase - .From() - .Filter(x => x.PlayerId, Operator.In, playerIds); - if (seasonStartYear != null) - { - mappingQuery = mappingQuery - .Filter(m => m.SeasonStartYear, Operator.Equals, seasonStartYear); - } - - var mappingRes = await mappingQuery.Get(); - //var mappingRes = await _supabase - // .From() - // .Where(m => m.SeasonStartYear == seasonStartYear) //ensure this is from the right season - // .Filter(x => x.PlayerId, Operator.In, playerIds) - // .Get(); - var mappings = mappingRes.Models; - - //Second, use those mappings to retrieve all of the weekly stats. - var mappingIds = mappings.Select(m => m.PlayerStatsId).ToList(); //used to get all stats associated with pulled players - var statsRes = await _supabase - .From() - .Filter(x => x.Id, Operator.In, mappingIds) - .Get(); - var stats = statsRes.Models; - - //Third, combine each player's weekly stats into a list and use all of the - // collected info to form the playerWithStats DTOs. - var playerTasks = players.Select(async player => - { - //get this player's season stats - var seasonStatsRes = await _supabase - .From() - .Where(s => s.Id == player.SeasonStatsId) - .Get(); - var seasonStats = seasonStatsRes.Model; - //get this player's stats mappings, making sure to not select the season stats - var playerMappings = mappings - .Where(m => m.PlayerId == player.Id && m.PlayerStatsId != player.SeasonStatsId) - .ToList(); - //Use these mappings to get all weekly stats and their associated week number - var weeklyStatDTOs = playerMappings.Select(m => - { - //for each mapping, get the associated stat - var stat = stats - .Where(s => s.Id == m.PlayerStatsId) - .Single(); - //from that stat, create a DTO - var statDTO = new DTOs.PlayerStat - { - Completions = stat.Completions, - PassingAttempts = stat.PassingAttempts, - PassingYards = stat.PassingYards, - PassingTds = stat.PassingTds, - InterceptionsAgainst = stat.InterceptionsAgainst, - SacksAgainst = stat.SacksAgainst, - FumblesAgainst = stat.FumblesAgainst, - PassingFirstDowns = stat.PassingFirstDowns, - PassingEpa = stat.PassingEpa, - Carries = stat.Carries, - RushingYards = stat.RushingYards, - RushingTds = stat.RushingTds, - RushingFirstDowns = stat.RushingFirstDowns, - RushingEpa = stat.RushingEpa, - Receptions = stat.Receptions, - Targets = stat.Targets, - ReceivingYards = stat.ReceivingYards, - ReceivingTds = stat.ReceivingTds, - ReceivingFirstDowns = stat.ReceivingFirstDowns, - ReceivingEpa = stat.ReceivingEpa, - FgMadeList = stat.FgMadeList, - FgMissedList = stat.FgMissedList, - FgBlockedList = stat.FgBlockedList, - PadAttempts = stat.PadAttempts, - PatPercent = stat.PatPercent, - FantasyPoints = stat.FantasyPoints, - FantasyPointsPpr = stat.FantasyPointsPpr - }; - //create a final DTO holding the stat DTO and its week number - var statWithWeek = new DTOs.PlayerStatWithWeekNum - { - Week = m.Week, - Stat = statDTO - }; - return statWithWeek; - }).ToList(); - - //return a DTO for this player with its stats - return new DTOs.PlayerWithStats - { - Player = new DTOs.Player - { - Id = player.Id, - Name = player.Name, - HeadshotUrl = player.HeadshotUrl, - Position = player.Position, - Status = player.Status, - StatusDescription = player.StatusDescription, - TeamId = player.TeamId, - SeasonStatsId = player.SeasonStatsId - }, - SeasonStat = new DTOs.PlayerStat - { - Completions = seasonStats.Completions, - PassingAttempts = seasonStats.PassingAttempts, - PassingYards = seasonStats.PassingYards, - PassingTds = seasonStats.PassingTds, - InterceptionsAgainst = seasonStats.InterceptionsAgainst, - SacksAgainst = seasonStats.SacksAgainst, - FumblesAgainst = seasonStats.FumblesAgainst, - PassingFirstDowns = seasonStats.PassingFirstDowns, - PassingEpa = seasonStats.PassingEpa, - Carries = seasonStats.Carries, - RushingYards = seasonStats.RushingYards, - RushingTds = seasonStats.RushingTds, - RushingFirstDowns = seasonStats.RushingFirstDowns, - RushingEpa = seasonStats.RushingEpa, - Receptions = seasonStats.Receptions, - Targets = seasonStats.Targets, - ReceivingYards = seasonStats.ReceivingYards, - ReceivingTds = seasonStats.ReceivingTds, - ReceivingFirstDowns = seasonStats.ReceivingFirstDowns, - ReceivingEpa = seasonStats.ReceivingEpa, - FgMadeList = seasonStats.FgMadeList, - FgMissedList = seasonStats.FgMissedList, - FgBlockedList = seasonStats.FgBlockedList, - PadAttempts = seasonStats.PadAttempts, - PatPercent = seasonStats.PatPercent, - FantasyPoints = seasonStats.FantasyPoints, - FantasyPointsPpr = seasonStats.FantasyPointsPpr - }, - WeeklyStats = weeklyStatDTOs - }; - }).ToList(); - - //wait for all player objects to finish being created and return the list - return (await Task.WhenAll(playerTasks)).ToList(); - - // //first, get the player DTO - // var playerRes = await _supabase - // .From() - // .Where(p => p.Id == playerID) - // .Get(); - // var player = playerRes.Model; - // var playerDTO = new DTOs.Player - // { - // Id = player.Id, - // Name = player.Name, - // HeadshotUrl = player.HeadshotUrl, - // Position = player.Position, - // Status = player.Status, - // StatusDescription = player.StatusDescription, - // TeamId = player.TeamId, - // SeasonStatsId = player.SeasonStatsId - // }; - - // //get the season stats DTO - // var seasonRes = await _supabase - // .From() - // .Where(s => s.Id == player.SeasonStatsId) - // .Get(); - // var seasonStats = seasonRes.Model; - // var seasonDTO = new DTOs.PlayerStat - // { - // Completions = seasonStats.Completions, - // PassingAttempts = seasonStats.PassingAttempts, - // PassingYards = seasonStats.PassingYards, - // PassingTds = seasonStats.PassingTds, - // InterceptionsAgainst = seasonStats.InterceptionsAgainst, - // SacksAgainst = seasonStats.SacksAgainst, - // FumblesAgainst = seasonStats.FumblesAgainst, - // PassingFirstDowns = seasonStats.PassingFirstDowns, - // PassingEpa = seasonStats.PassingEpa, - // Carries = seasonStats.Carries, - // RushingYards = seasonStats.RushingYards, - // RushingTds = seasonStats.RushingTds, - // RushingFirstDowns = seasonStats.RushingFirstDowns, - // RushingEpa = seasonStats.RushingEpa, - // Receptions = seasonStats.Receptions, - // Targets = seasonStats.Targets, - // ReceivingYards = seasonStats.ReceivingYards, - // ReceivingTds = seasonStats.ReceivingTds, - // ReceivingFirstDowns = seasonStats.ReceivingFirstDowns, - // ReceivingEpa = seasonStats.ReceivingEpa, - // FgMadeList = seasonStats.FgMadeList, - // FgMissedList = seasonStats.FgMissedList, - // FgBlockedList = seasonStats.FgBlockedList, - // PadAttempts = seasonStats.PadAttempts, - // PatPercent = seasonStats.PatPercent, - // FantasyPoints = seasonStats.FantasyPoints, - // FantasyPointsPpr = seasonStats.FantasyPointsPpr - // }; - - // //get the list of weekly stats DTOs - // var mappingsRes = await _supabase - // .From() - // .Where(m => m.PlayerId == playerID) - // .Get(); - // var mappings = mappingsRes.Models; //get the weekly stats mappings - // //use the mappings to get the weekly stats objects and their associated week - // var weeklyStatTasks = mappings.Select(async m => - // { - // //for each mapping, get the associated stat - // var statRes = await _supabase - // .From() - // .Where(s => s.Id == m.PlayerStatsId) - // .Get(); - // var stat = statRes.Model; - // //from that stat, create a DTO - // var statDTO = new DTOs.PlayerStat - // { - // Completions = stat.Completions, - // PassingAttempts = stat.PassingAttempts, - // PassingYards = stat.PassingYards, - // PassingTds = stat.PassingTds, - // InterceptionsAgainst = stat.InterceptionsAgainst, - // SacksAgainst = stat.SacksAgainst, - // FumblesAgainst = stat.FumblesAgainst, - // PassingFirstDowns = stat.PassingFirstDowns, - // PassingEpa = stat.PassingEpa, - // Carries = stat.Carries, - // RushingYards = stat.RushingYards, - // RushingTds = stat.RushingTds, - // RushingFirstDowns = stat.RushingFirstDowns, - // RushingEpa = stat.RushingEpa, - // Receptions = stat.Receptions, - // Targets = stat.Targets, - // ReceivingYards = stat.ReceivingYards, - // ReceivingTds = stat.ReceivingTds, - // ReceivingFirstDowns = stat.ReceivingFirstDowns, - // ReceivingEpa = stat.ReceivingEpa, - // FgMadeList = stat.FgMadeList, - // FgMissedList = stat.FgMissedList, - // FgBlockedList = stat.FgBlockedList, - // PadAttempts = stat.PadAttempts, - // PatPercent = stat.PatPercent, - // FantasyPoints = stat.FantasyPoints, - // FantasyPointsPpr = stat.FantasyPointsPpr - // }; - // //create a final DTO holding the stat DTO and its week number - // var statWithWeek = new DTOs.PlayerStatWithWeekNum - // { - // Week = m.Week, - // Stat = statDTO - // }; - // return statWithWeek; - // }).ToList(); - // //wait on all weekly stat DTOs to generate - // var weeklyStatDTOs = (await Task.WhenAll(weeklyStatTasks)).ToList(); - - // //compile all player data - // var playerWithStats = new DTOs.PlayerWithStats - // { - // Player = playerDTO, - // SeasonStat = seasonDTO, - // WeeklyStats = weeklyStatDTOs - // }; - - // return playerWithStats; - //} - } - } -} diff --git a/Fantasy-Football-Assistant-Manager/Controllers/TeamSeasonStatController.cs b/Fantasy-Football-Assistant-Manager/Controllers/TeamSeasonStatController.cs deleted file mode 100644 index cbead8a..0000000 --- a/Fantasy-Football-Assistant-Manager/Controllers/TeamSeasonStatController.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Fantasy_Football_Assistant_Manager.Models.Supabase; -using Fantasy_Football_Assistant_Manager.Services; -using Fantasy_Football_Assistant_Manager.Utils; -using Microsoft.AspNetCore.Mvc; -using Supabase; - -namespace Fantasy_Football_Assistant_Manager.Controllers; - -public class TeamSeasonStatController : ControllerBase -{ - private readonly NflVerseService _nflVerseService; - private readonly Client _supabase; - - public TeamSeasonStatController(NflVerseService nflVerseService, Client supabase) - { - _nflVerseService = nflVerseService; - _supabase = supabase; - } - - [HttpPost("all")] - public async Task PostAll() - { - try - { - // fetch team stats using service - var stats = await _nflVerseService.GetAllTeamSeasonStatsAsync(); - if (stats == null || !stats.Any()) - { - return BadRequest("No team stats found to insert."); - } - - // break up the stats into offensive and defensive stats, keeping track of the new ids - List offensiveStats = new List(); - List defensiveStats = new List(); - Dictionary teamOffensiveStatIdMap = new Dictionary(); - Dictionary teamDefensiveStatIdMap = new Dictionary(); - - - foreach (var stat in stats) - { - if (stat == null) continue; - - // add the ids to the corresponding maps - Guid offensiveId = Guid.NewGuid(); - Guid defensiveId = Guid.NewGuid(); - teamOffensiveStatIdMap[stat.Team] = offensiveId; - teamDefensiveStatIdMap[stat.Team] = defensiveId; - - // parse out the offensive stats and add to list - var offensiveStat = new TeamOffensiveStat - { - Id = offensiveId, - Completions = ControllerHelpers.NullIfZero(stat.Completions), - Attempts = ControllerHelpers.NullIfZero(stat.Attempts), - PassingYards = ControllerHelpers.NullIfZero(stat.PassingYards), - PassingTds = ControllerHelpers.NullIfZero(stat.PassingTds), - PassingInterceptions = ControllerHelpers.NullIfZero(stat.PassingInterceptions), - SacksAgainst = ControllerHelpers.NullIfZero(stat.SacksAgainst), - FumblesAgainst = ControllerHelpers.NullIfZero(stat.FumblesAgainst), - Carries = ControllerHelpers.NullIfZero(stat.Carries), - RushingYards = ControllerHelpers.NullIfZero(stat.RushingYards), - RushingTds = ControllerHelpers.NullIfZero(stat.RushingTds), - Receptions = ControllerHelpers.NullIfZero(stat.Receptions), - Targets = ControllerHelpers.NullIfZero(stat.Targets), - ReceivingYards = ControllerHelpers.NullIfZero(stat.ReceivingYards), - ReceivingTds = ControllerHelpers.NullIfZero(stat.ReceivingTds) - }; - - offensiveStats.Add(offensiveStat); - - // parse out the defensive stats and add to list - var defensiveStat = new TeamDefensiveStat - { - Id = defensiveId, - TacklesForLoss = ControllerHelpers.NullIfZero(stat.TacklesForLoss), - TacklesForLossYards = ControllerHelpers.NullIfZero(stat.TacklesForLossYards), - FumblesFor = ControllerHelpers.NullIfZero(stat.FumblesFor), - SacksFor = ControllerHelpers.NullIfZero(stat.SacksFor), - SackYardsFor = ControllerHelpers.NullIfZero(stat.SackYardsFor), - InterceptionsFor = ControllerHelpers.NullIfZero(stat.InterceptionsFor), - InterceptionYardsFor = ControllerHelpers.NullIfZero(stat.InterceptionYardsFor), - DefTds = ControllerHelpers.NullIfZero(stat.DefTds), - Safeties = ControllerHelpers.NullIfZero(stat.Safeties), - PassDefended = ControllerHelpers.NullIfZero(stat.PassDefended) - }; - - defensiveStats.Add(defensiveStat); - } - - // get existing teams from supabase - var teamsResponse = await _supabase - .From() - .Get(); - - if (teamsResponse.Models == null || !teamsResponse.Models.Any()) - { - return NotFound("No teams found in the database"); - } - - var existingTeams = teamsResponse.Models; - - // create updated teams list - var updatedTeams = existingTeams - .Where(t => teamOffensiveStatIdMap.ContainsKey(t.Id)) - .Select(t => - new Team - { - Id = t.Id, - Name = t.Name, - Conference = t.Conference, - Division = t.Division, - LogoUrl = t.LogoUrl, - OffensiveStatsId = teamOffensiveStatIdMap[t.Id], - DefensiveStatsId = teamDefensiveStatIdMap[t.Id] - } - ) - .ToList(); - - // insert the offensive stats - await _supabase - .From() - .Insert(offensiveStats); - - // insert the defensive stats - await _supabase - .From() - .Insert(defensiveStats); - - // update the team records to reference the stats - await _supabase - .From() - .OnConflict(x => x.Id) - .Upsert(updatedTeams); - - return Ok(new { message = "Team stats inserted successfully!" }); - } - catch (Exception ex) - { - return StatusCode(500, $"Error populating team stats: {ex.Message}"); - } - } - - [HttpDelete("all")] - public async Task DeleteAll() - { - try - { - // delete all offensive stats - await _supabase - .From() - .Where(s => s.Id != null) - .Delete(); - - // delete all defensive stats - await _supabase - .From() - .Where(s => s.Id != null) - .Delete(); - - // the foreign keys set to null automatically in teams table - return Ok(new { message = "Successfully deleted all team stats" }); - } catch (Exception ex) - { - return StatusCode(500, $"Error deleting all team stats: {ex.Message}"); - } - } -} diff --git a/Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.http b/Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.http deleted file mode 100644 index e093922..0000000 --- a/Fantasy-Football-Assistant-Manager/Fantasy-Football-Assistant-Manager.http +++ /dev/null @@ -1,6 +0,0 @@ -@Fantasy_Football_Assistant_Manager_HostAddress = http://localhost:5090 - -GET {{Fantasy_Football_Assistant_Manager_HostAddress}}/weatherforecast/ -Accept: application/json - -###