├── .editorconfig
├── .gitignore
├── CLI
├── R5.FFDB.CLI.Tests
│ ├── R5.FFDB.CLI.Tests.csproj
│ └── Tests
│ │ ├── FfdbConfigValidationTests.cs
│ │ ├── ParserTests.cs
│ │ └── RunInfoBuilderTests.cs
└── R5.FFDB.CLI
│ ├── AssemblyInfo.cs
│ ├── Commands
│ ├── AddStats.cs
│ ├── InitialSetup.cs
│ ├── RunInfoBase.cs
│ ├── UpdateRosteredPlayers.cs
│ ├── UpdateRosters.cs
│ └── ViewState.cs
│ ├── Configuration
│ ├── FfdbConfig.cs
│ └── FileConfigResolver.cs
│ ├── ConfigureRunInfoBuilder.cs
│ ├── Engine
│ ├── EngineResolver.cs
│ └── EngineRunner.cs
│ ├── Program.cs
│ ├── R5.FFDB.CLI.csproj
│ ├── ffdb_config.template.json
│ └── prerelease_todos.txt
├── DatabaseProviders
├── R5.FFDB.DbProviders.Mongo
│ ├── AssemblyInfo.cs
│ ├── Collections
│ │ ├── Attributes.cs
│ │ ├── CollectionNames.cs
│ │ ├── CollectionResolver.cs
│ │ └── Indexes.cs
│ ├── DatabaseContext
│ │ ├── DbContext.cs
│ │ ├── DbContextBase.cs
│ │ ├── PlayerDbContext.cs
│ │ ├── PlayerStatsDbContext.cs
│ │ ├── TeamDbContext.cs
│ │ ├── TeamStatsDbContext.cs
│ │ ├── UpdateLogDbContext.cs
│ │ └── WeekMatchupsDbContext.cs
│ ├── Documents
│ │ ├── DocumentBase.cs
│ │ ├── PlayerDocument.cs
│ │ ├── TeamDocument.cs
│ │ ├── UpdateLogDocument.cs
│ │ ├── WeekMatchupDocument.cs
│ │ ├── WeekStatsDstDocument.cs
│ │ ├── WeekStatsPlayerDocument.cs
│ │ └── WeekStatsTeamDocument.cs
│ ├── Models
│ │ └── MongoWeekStatType.cs
│ ├── MongoConfig.cs
│ ├── MongoDbContext.cs
│ ├── MongoDbProvider.cs
│ ├── R5.FFDB.DbProviders.Mongo.csproj
│ ├── README.md
│ └── Serialization
│ │ ├── MongoSerializers.cs
│ │ └── Serializers
│ │ ├── DateTimeOffsetSerializer.cs
│ │ └── GuidSerializer.cs
└── R5.FFDB.DbProviders.PostgreSql
│ ├── AssemblyInfo.cs
│ ├── DatabaseContext
│ ├── DbContext.cs
│ ├── DbContextBase.cs
│ ├── PlayerDbContext.cs
│ ├── PlayerStatsDbContext.cs
│ ├── TeamDbContext.cs
│ ├── TeamStatsDbContext.cs
│ ├── UpdateLogDbContext.cs
│ └── WeekMatchupsDbContext.cs
│ ├── DatabaseProvider
│ ├── PostgresConfig.cs
│ └── PostgresDbProvider.cs
│ ├── Entities
│ ├── PlayerSql.cs
│ ├── PlayerTeamMapSql.cs
│ ├── TeamGameStatsSql.cs
│ ├── TeamSql.cs
│ ├── UpdateLogSql.cs
│ ├── WeekGameMatchupSql.cs
│ └── WeekStats
│ │ ├── WeekStatsDstSql.cs
│ │ ├── WeekStatsIdpSql.cs
│ │ ├── WeekStatsKickSql.cs
│ │ ├── WeekStatsMiscSql.cs
│ │ ├── WeekStatsPassSql.cs
│ │ ├── WeekStatsReceiveSql.cs
│ │ ├── WeekStatsReturnSql.cs
│ │ ├── WeekStatsRushSql.cs
│ │ └── WeekStatsSqlExtensions.cs
│ ├── R5.FFDB.DbProviders.PostgreSql.csproj
│ ├── README.md
│ └── TableNames.cs
├── DevTester
├── DevProgram.cs
├── DevTestServiceProvider.cs
├── DevTester.csproj
├── Temp
│ ├── depthChart_NFL.COM_feeds.json
│ ├── seahawks_roster_NFL.com.html
│ └── teams_NFL.COM.json
├── Timer.cs
└── devnotes.txt
├── Documentation
├── EngineDataFlow.png
├── ffdb-engine_core-data-source-flow.drawio
├── ffdb_logo.png
├── ffdb_logo.psd
└── flow_diagram_readme.txt
├── Engine
├── R5.FFDB.Components
│ ├── AppLogger.cs
│ ├── Configurations
│ │ ├── LoggingConfig.cs
│ │ ├── ProgramOptions.cs
│ │ └── WebRequestConfig.cs
│ ├── CoreData
│ │ ├── CoreDataSource.cs
│ │ ├── Dynamic
│ │ │ └── Rosters
│ │ │ │ ├── Models
│ │ │ │ └── RosterCacheData.cs
│ │ │ │ ├── RosterCache.cs
│ │ │ │ └── Sources
│ │ │ │ └── V1
│ │ │ │ ├── Mappers
│ │ │ │ ├── ToCoreMapper.cs
│ │ │ │ └── ToVersionedMapper.cs
│ │ │ │ ├── Models
│ │ │ │ └── RosterVersioned.cs
│ │ │ │ ├── RosterScraper.cs
│ │ │ │ └── RosterSource.cs
│ │ ├── ServicesExtensions.cs
│ │ └── Static
│ │ │ ├── PlayerStats
│ │ │ └── Sources
│ │ │ │ └── V1
│ │ │ │ ├── Mappers
│ │ │ │ ├── SourceJsonReader.cs
│ │ │ │ ├── ToCoreMapper.cs
│ │ │ │ └── ToVersionedMapper.cs
│ │ │ │ ├── Models
│ │ │ │ └── PlayerWeekStatsVersioned.cs
│ │ │ │ └── PlayerWeekStatsSource.cs
│ │ │ ├── Players
│ │ │ ├── Add
│ │ │ │ └── Sources
│ │ │ │ │ └── V1
│ │ │ │ │ ├── Mappers
│ │ │ │ │ ├── ToCoreMapper.cs
│ │ │ │ │ └── ToVersionedMapper.cs
│ │ │ │ │ ├── Models
│ │ │ │ │ └── PlayerAddVersioned.cs
│ │ │ │ │ ├── PlayerAddSource.cs
│ │ │ │ │ └── PlayerScraper.cs
│ │ │ └── PlayerIdMappings.cs
│ │ │ ├── ReceiverTargets
│ │ │ └── PlayerMatcher
│ │ │ │ ├── EditDistance.cs
│ │ │ │ └── PlayerMatcherFactory.cs
│ │ │ ├── TeamStats
│ │ │ ├── Models
│ │ │ │ ├── TeamWeekStatsCacheData.cs
│ │ │ │ └── TeamWeekStatsSourceModel.cs
│ │ │ ├── Sources
│ │ │ │ └── V1
│ │ │ │ │ ├── Mappers
│ │ │ │ │ ├── ToCoreMapper.cs
│ │ │ │ │ └── ToVersionedMapper.cs
│ │ │ │ │ ├── Models
│ │ │ │ │ └── TeamWeekStatsVersioned.cs
│ │ │ │ │ └── TeamWeekStatsSource.cs
│ │ │ └── TeamWeekStatsCache.cs
│ │ │ └── WeekMatchups
│ │ │ ├── Sources
│ │ │ └── V1
│ │ │ │ ├── Mappers
│ │ │ │ ├── ToCoreMapper.cs
│ │ │ │ └── ToVersionedMapper.cs
│ │ │ │ ├── Models
│ │ │ │ └── WeekMatchupsVersioned.cs
│ │ │ │ └── WeekMatchupSource.cs
│ │ │ └── WeekMatchupsCache.cs
│ ├── DataDirectoryPath.cs
│ ├── Exceptions.cs
│ ├── Extensions
│ │ └── JsonConverters
│ │ │ └── WeekInfoJsonConverter.cs
│ ├── Http
│ │ ├── Endpoints.cs
│ │ ├── WebRequestClient.cs
│ │ └── WebRequestThrottle.cs
│ ├── Pipelines
│ │ ├── CommonStages
│ │ │ └── FetchPlayersStage.cs
│ │ ├── Pipeline.cs
│ │ ├── Players
│ │ │ └── UpdateCurrentlyRosteredPipeline.cs
│ │ ├── Setup
│ │ │ └── InitialSetupPipeline.cs
│ │ ├── Stage.cs
│ │ ├── Stats
│ │ │ ├── AddForWeekPipeline.cs
│ │ │ └── UpdateMissingPipeline.cs
│ │ └── Teams
│ │ │ └── UpdateRosterMappingsPipeline.cs
│ ├── R5.FFDB.Components.csproj
│ └── ValueProviders
│ │ ├── AvailableWeeksValue.cs
│ │ └── LatestWeekValue.cs
├── R5.FFDB.Core
│ ├── Database
│ │ ├── IDatabaseContext.cs
│ │ └── IDatabaseProvider.cs
│ ├── Entities
│ │ ├── Player.cs
│ │ ├── PlayerAdd.cs
│ │ ├── PlayerUpdate.cs
│ │ ├── PlayerWeekStats.cs
│ │ ├── Roster.cs
│ │ ├── Team.cs
│ │ ├── TeamWeekStats.cs
│ │ └── WeekMatchup.cs
│ ├── IAppLogger.cs
│ ├── LICENSE
│ ├── Models
│ │ ├── Position.cs
│ │ ├── RosterStatus.cs
│ │ ├── WeekInfo.cs
│ │ └── WeekStatType.cs
│ ├── R5.FFDB.Core.csproj
│ ├── R5.FFDB.Core.nuspec
│ ├── README.md
│ ├── Teams.cs
│ └── WeekStatCategory.cs
└── R5.FFDB.Engine
│ ├── ConfigBuilders
│ ├── LoggingConfigBuilder.cs
│ └── WebRequestConfigBuilder.cs
│ ├── EngineBaseServiceCollection.cs
│ ├── EngineSetup.cs
│ ├── FfdbEngine.cs
│ ├── LICENSE
│ ├── Processors
│ ├── PlayerProcessor.cs
│ ├── StatsProcessor.cs
│ └── TeamProcessor.cs
│ ├── R5.FFDB.Engine.csproj
│ └── R5.FFDB.Engine.nuspec
├── FFDB.sln
├── LICENSE
├── R5.Internals
├── R5.Internals.Abstractions
│ ├── Expressions
│ │ └── MisterInspector.cs
│ ├── Pipeline
│ │ ├── AsyncPipeline.cs
│ │ ├── AsyncPipelineStage.cs
│ │ └── ProcessStageResult.cs
│ ├── R5.Internals.Abstractions.csproj
│ ├── SystemConsole
│ │ └── ConsoleManager.cs
│ └── Utilities
│ │ └── RangedListBuilder.cs
├── R5.Internals.Caching
│ ├── Caches
│ │ └── AsyncLazyCache.cs
│ ├── R5.Internals.Caching.csproj
│ └── ValueProviders
│ │ └── AsyncValueProvider.cs
├── R5.Internals.Extensions
│ ├── Collections
│ │ └── ListExtensions.cs
│ ├── DependencyInjection
│ │ └── ServiceProviderExtensions.cs
│ ├── R5.Internals.Extensions.csproj
│ ├── Reflection
│ │ ├── ExpressionExtensions.cs
│ │ ├── MemberInfoExtensions.cs
│ │ └── TypeExtensions.cs
│ └── Serialization
│ │ └── NewtonsoftExtensions.cs
├── R5.PostgresMapper
│ ├── AssemblyInfo.cs
│ ├── Attributes
│ │ ├── ColumnAttributes.cs
│ │ └── TableAttributes.cs
│ ├── DbConnection.cs
│ ├── DebugUtil.cs
│ ├── Mappers
│ │ ├── DbValueToObjectMapper.cs
│ │ ├── ToDbValueStringMapper.cs
│ │ └── ToPostgresDataTypeMapper.cs
│ ├── MetadataResolver.cs
│ ├── Models
│ │ ├── PostgresDataType.cs
│ │ ├── SqlEntity.cs
│ │ └── TableColumn.cs
│ ├── NpgsqlExtensions.cs
│ ├── QueryCommand
│ │ ├── CreateTableCommand.cs
│ │ ├── DeleteCommand.cs
│ │ ├── DeleteWhereCommand.cs
│ │ ├── ExistsQuery.cs
│ │ ├── InsertCommand.cs
│ │ ├── PropertySelectionResolver.cs
│ │ ├── SelectQuery.cs
│ │ ├── TableExistsQuery.cs
│ │ ├── TruncateCommand.cs
│ │ ├── UnionSelectQuery.cs
│ │ └── UpdateCommand.cs
│ ├── R5.Internals.PostgresMapper.csproj
│ ├── SqlBuilders
│ │ ├── ConcatSqlBuilder.cs
│ │ └── WhereConditionBuilder.cs
│ ├── SupportedTypes.cs
│ ├── System
│ │ └── Tables
│ │ │ └── InformationSchema.cs
│ └── todos.txt
└── Tests
│ ├── R5.Internals.Caching.Tests
│ ├── AsyncLazyCacheTests.cs
│ └── R5.Internals.Caching.Tests.csproj
│ └── R5.Internals.PostgresMapper.Tests
│ ├── Extensions.cs
│ ├── R5.Internals.PostgresMapper.Tests.csproj
│ └── Tests
│ └── SqlBuilders
│ └── WhereConditionBuilderTests.cs
├── README.md
└── appveyor.yml
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | indent_size = 4
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI.Tests/R5.FFDB.CLI.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | R5.FFDB.CLI.Tests
6 | R5.FFDB.CLI.Tests
7 | Debug;Release;Test
8 |
9 |
10 |
11 | latest
12 |
13 |
14 |
15 | latest
16 |
17 |
18 |
19 | latest
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI.Tests/Tests/ParserTests.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 | using R5.RunInfoBuilder;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using Xunit;
7 |
8 | namespace R5.FFDB.CLI.Tests.Tests
9 | {
10 | public class ParserTests
11 | {
12 | public class NullableWeekInfo
13 | {
14 | [Theory]
15 | [InlineData("")]
16 | [InlineData("20185")]
17 | [InlineData("2018-5-5")]
18 | [InlineData("text-5")]
19 | [InlineData("5-text")]
20 | [InlineData("text-text")]
21 | public void Invalid_Returns_FalseAndDefault(string value)
22 | {
23 | var builder = ConfigureRunInfoBuilder.Create();
24 |
25 | bool valid = builder.Parser.TryParseAs(value, out WeekInfo? parsed);
26 |
27 | Assert.False(valid);
28 | Assert.Equal(default, parsed);
29 | }
30 |
31 | [Theory]
32 | [InlineData("2018-5", 2018, 5)]
33 | [InlineData("2018-15", 2018, 15)]
34 | public void Valid_Returns_TrueWithCorrectParsedValue(string value,
35 | int expectedSeason, int expectedWeek)
36 | {
37 | var builder = ConfigureRunInfoBuilder.Create();
38 |
39 | bool valid = builder.Parser.TryParseAs(value, out WeekInfo? parsed);
40 |
41 | Assert.True(valid);
42 | Assert.Equal(new WeekInfo(expectedSeason, expectedWeek), parsed);
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: InternalsVisibleTo("R5.FFDB.CLI.Tests")]
5 | [assembly: CLSCompliant(true)]
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/Commands/AddStats.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 | using R5.RunInfoBuilder;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace R5.FFDB.CLI.Commands
8 | {
9 | public static class AddStats
10 | {
11 | private const string _commandKey = "add-stats";
12 |
13 | public class RunInfo : RunInfoBase
14 | {
15 | public override string CommandKey => _commandKey;
16 | public override string Description => "Adds player and team stats for either a specified week, or for all available and missing.";
17 |
18 | public WeekInfo? Week { get; set; }
19 | }
20 |
21 | internal static Command GetCommand()
22 | {
23 | var command = new Command
24 | {
25 | Key = _commandKey,
26 | SubCommands =
27 | {
28 | new SubCommand
29 | {
30 | Key = "missing"
31 | },
32 | new SubCommand
33 | {
34 | Key = "week",
35 | Arguments =
36 | {
37 | new PropertyArgument
38 | {
39 | Property = ri => ri.Week,
40 | HelpToken = ""
41 | }
42 | }
43 | }
44 | },
45 | GlobalOptions =
46 | {
47 | new Option
48 | {
49 | Key = "save-to-disk",
50 | Property = ri => ri.SaveToDisk
51 | },
52 | new Option
53 | {
54 | Key = "save-src-files",
55 | Property = ri => ri.SaveOriginalSourceFiles
56 | }
57 | }
58 | };
59 |
60 | RunInfoBase.AddCommonOptions(command);
61 | return command;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/Commands/InitialSetup.cs:
--------------------------------------------------------------------------------
1 | using R5.RunInfoBuilder;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace R5.FFDB.CLI.Commands
7 | {
8 | public static class InitialSetup
9 | {
10 | private const string _commandKey = "setup";
11 |
12 | public class RunInfo : RunInfoBase
13 | {
14 | public override string CommandKey => _commandKey;
15 | public override string Description => "Runs initial database setup (ie creating tables, etc). Can optionally add all missing stats afterwards.";
16 |
17 | public bool SkipAddingStats { get; set; }
18 | }
19 |
20 | internal static Command GetCommand()
21 | {
22 | var command = new Command
23 | {
24 | Key = _commandKey,
25 | Options =
26 | {
27 | new Option
28 | {
29 | Key = "skip-stats",
30 | Property = ri => ri.SkipAddingStats
31 | }
32 | }
33 | };
34 |
35 | RunInfoBase.AddCommonOptions(command);
36 | return command;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/Commands/RunInfoBase.cs:
--------------------------------------------------------------------------------
1 | using R5.RunInfoBuilder;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace R5.FFDB.CLI.Commands
7 | {
8 | public abstract class RunInfoBase
9 | {
10 | public abstract string CommandKey { get; }
11 | public abstract string Description { get; }
12 |
13 | public string ConfigFilePath { get; set; }
14 | public bool SkipRosterFetch { get; set; }
15 | public bool SaveToDisk { get; set; }
16 | public bool SaveOriginalSourceFiles { get; set; }
17 |
18 | public static void AddCommonOptions(Command command)
19 | where T : RunInfoBase
20 | {
21 | if (command.GlobalOptions == null)
22 | {
23 | command.GlobalOptions = new List>();
24 | }
25 |
26 | command.GlobalOptions.Add(new Option
27 | {
28 | Key = "config | c",
29 | Property = ri => ri.ConfigFilePath
30 | });
31 |
32 | command.GlobalOptions.Add(new Option
33 | {
34 | Key = "skip-roster | s",
35 | Property = ri => ri.SkipRosterFetch
36 | });
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/Commands/UpdateRosteredPlayers.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 | using R5.RunInfoBuilder;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace R5.FFDB.CLI.Commands
8 | {
9 | public static class UpdateRosteredPlayers
10 | {
11 | private const string _commandKey = "update-players";
12 |
13 | public class RunInfo : RunInfoBase
14 | {
15 | public override string CommandKey => _commandKey;
16 | public override string Description => "Updates dynamic information for players currently rostered on a team.";
17 | }
18 |
19 | internal static Command GetCommand()
20 | {
21 | var command = new Command
22 | {
23 | Key = _commandKey
24 | };
25 |
26 | RunInfoBase.AddCommonOptions(command);
27 | return command;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/Commands/UpdateRosters.cs:
--------------------------------------------------------------------------------
1 | using R5.RunInfoBuilder;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace R5.FFDB.CLI.Commands
7 | {
8 | public static class UpdateRosters
9 | {
10 | private const string _commandKey = "update-rosters";
11 |
12 | public class RunInfo : RunInfoBase
13 | {
14 | public override string CommandKey => _commandKey;
15 | public override string Description => "Updates mappings between players-and-teams, based off of a current fetch of team roster information.";
16 | }
17 |
18 | internal static Command GetCommand()
19 | {
20 | var command = new Command
21 | {
22 | Key = _commandKey
23 | };
24 |
25 | RunInfoBase.AddCommonOptions(command);
26 | return command;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/Commands/ViewState.cs:
--------------------------------------------------------------------------------
1 | using R5.RunInfoBuilder;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace R5.FFDB.CLI.Commands
7 | {
8 | public static class ViewState
9 | {
10 | private const string _commandKey = "view-state";
11 |
12 | public class RunInfo : RunInfoBase
13 | {
14 | public override string CommandKey => _commandKey;
15 | public override string Description => "Will acquire and display general state information regarding the app and the NFL season.";
16 | }
17 |
18 | internal static Command GetCommand()
19 | {
20 | var command = new Command
21 | {
22 | Key = _commandKey
23 | };
24 |
25 | RunInfoBase.AddCommonOptions(command);
26 | return command;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/Configuration/FileConfigResolver.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Serilog;
3 | using Serilog.Events;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Text;
8 |
9 | namespace R5.FFDB.CLI.Configuration
10 | {
11 | internal static class FileConfigResolver
12 | {
13 | internal static FfdbConfig FromFile(string filePath)
14 | {
15 | // default search for file in same directory as program
16 | if (string.IsNullOrWhiteSpace(filePath))
17 | {
18 | filePath = Path.Combine(Directory.GetCurrentDirectory(), "ffdb_config.json");
19 | }
20 |
21 | if (!File.Exists(filePath))
22 | {
23 | throw new ArgumentException($"FFDB config file doesn't exist at '{filePath}'.");
24 | }
25 |
26 | var settings = new JsonSerializerSettings
27 | {
28 | // prevent invalid properties on config json
29 | MissingMemberHandling = MissingMemberHandling.Error
30 | };
31 |
32 | FfdbConfig config;
33 | try
34 | {
35 | config = JsonConvert.DeserializeObject(File.ReadAllText(filePath), settings);
36 | }
37 | catch (Exception ex)
38 | {
39 | throw new InvalidOperationException("Failed to parse FFDB config from file.", ex);
40 | }
41 |
42 | config.ThrowIfInvalid();
43 |
44 | return config;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/R5.FFDB.CLI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.2
6 | latest
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/ffdb_config.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "RootDataPath": "",
3 | "WebRequest": {
4 | "ThrottleMilliseconds": 1000,
5 | "RandomizedThrottle": {
6 | "Min": 1000,
7 | "Max": 3000
8 | }
9 | },
10 | "Logging": {
11 | "Directory": "",
12 | "MaxBytes": null,
13 | "RollingInterval": "Day",
14 | "RollOnFileSizeLimit": false,
15 | "UseDebugLogLevel": false
16 | },
17 | "PostgreSql": {
18 | "Host": "",
19 | "DatabaseName": "",
20 | "Username": "",
21 | "Password": ""
22 | },
23 | "Mongo": {
24 | "ConnectionString": "",
25 | "DatabaseName": ""
26 | }
27 | }
--------------------------------------------------------------------------------
/CLI/R5.FFDB.CLI/prerelease_todos.txt:
--------------------------------------------------------------------------------
1 | Update the HasBeenInitialized for the db providers.
2 | it should check that all of the following are met:
3 | - ALL tables/collections have been created
4 | - ALL teams have been added
5 |
6 | - documentation
7 | - get image/diagram of db schemas (both mongo and postgres) for alpha release discussion
8 |
9 |
10 |
11 | add readme docs and c# comments
12 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | [assembly: CLSCompliant(true)]
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Collections/Attributes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace R5.FFDB.DbProviders.Mongo.Collections
4 | {
5 | public class CollectionNameAttribute : Attribute
6 | {
7 | public string Name { get; }
8 |
9 | public CollectionNameAttribute(string name)
10 | {
11 | Name = name;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Collections/CollectionNames.cs:
--------------------------------------------------------------------------------
1 | namespace R5.FFDB.DbProviders.Mongo.Collections
2 | {
3 | public static class Collection
4 | {
5 | internal const string FfdbPrefix = "ffdb.";
6 |
7 | internal const string Player = FfdbPrefix + "player";
8 | internal const string Team = FfdbPrefix + "team";
9 | internal const string UpdateLog = FfdbPrefix + "updateLog";
10 | internal const string WeekMatchup = FfdbPrefix + "weekMatchup";
11 | internal const string WeekStatsDst = FfdbPrefix + "weekStatsDst";
12 | internal const string WeekStatsPlayer = FfdbPrefix + "weekStatsPlayer";
13 | internal const string WeekStatsTeam = FfdbPrefix + "weekStatsTeam";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Collections/CollectionResolver.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Driver;
2 | using R5.FFDB.DbProviders.Mongo.Documents;
3 | using R5.Internals.Extensions.Reflection;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Reflection;
8 |
9 | namespace R5.FFDB.DbProviders.Mongo.Collections
10 | {
11 | public static class CollectionResolver
12 | {
13 | public static List GetAllNames()
14 | {
15 | return CollectionResolver.GetDocumentTypes()
16 | .Select(GetName)
17 | .ToList();
18 | }
19 |
20 | public static string GetName()
21 | where T : DocumentBase
22 | {
23 | return GetName(typeof(T));
24 | }
25 |
26 | public static string GetName(Type type)
27 | {
28 | if (!type.IsClass || type.IsAbstract || !type.IsSubclassOf(typeof(DocumentBase)))
29 | {
30 | throw new ArgumentException($"Type must be a non-abstract class deriving from '{nameof(DocumentBase)}'.");
31 | }
32 |
33 | CollectionNameAttribute attr = type.GetCustomAttributeOrNull();
34 |
35 | if (attr == null)
36 | {
37 | throw new InvalidOperationException($"Document '{type.Name}' is missing its collection name.");
38 | }
39 |
40 | return attr.Name;
41 | }
42 |
43 | public static List GetDocumentTypes()
44 | {
45 | return Assembly.GetAssembly(typeof(DocumentBase))
46 | .GetTypes()
47 | .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(DocumentBase))
48 | && t.GetCustomAttributeOrNull() != null)
49 | .ToList();
50 | }
51 |
52 | public static IMongoCollection Get(IMongoDatabase database)
53 | where T : DocumentBase
54 | {
55 | var name = GetName();
56 |
57 | return database.GetCollection(name);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Collections/Indexes.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Driver;
2 | using R5.FFDB.DbProviders.Mongo.Documents;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace R5.FFDB.DbProviders.Mongo.Collections
9 | {
10 | public static class Indexes
11 | {
12 | public static Task CreateForTypeAsync(Type documentType, IMongoDatabase db)
13 | {
14 | if (!_createIndexFuncMap.TryGetValue(documentType, out Func createIndexFunc))
15 | {
16 | throw new InvalidOperationException($"Type '{documentType.Name}' doesn't have an associated create index func.");
17 | }
18 |
19 | return createIndexFunc(db);
20 | }
21 |
22 | private static Dictionary> _createIndexFuncMap = new Dictionary>
23 | {
24 | { typeof(PlayerDocument), db => PlayerDocument.CreateIndexAsync(db) },
25 | { typeof(TeamDocument), db => TeamDocument.CreateIndexAsync(db) },
26 | { typeof(WeekStatsTeamDocument), db => WeekStatsTeamDocument.CreateIndexAsync(db) },
27 | { typeof(UpdateLogDocument), db => UpdateLogDocument.CreateIndexAsync(db) },
28 | { typeof(WeekStatsPlayerDocument), db => WeekStatsPlayerDocument.CreateIndexAsync(db) },
29 | { typeof(WeekStatsDstDocument), db => WeekStatsDstDocument.CreateIndexAsync(db) },
30 | { typeof(WeekMatchupDocument), db => WeekMatchupDocument.CreateIndexAsync(db) }
31 | };
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/DatabaseContext/DbContextBase.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Components;
4 | using R5.FFDB.Core;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace R5.FFDB.DbProviders.Mongo.DatabaseContext
10 | {
11 | public abstract class DbContextBase
12 | {
13 | protected Func GetDatabase { get; }
14 | protected IAppLogger Logger { get; }
15 |
16 | protected DbContextBase(
17 | Func getDatabase,
18 | IAppLogger logger)
19 | {
20 | GetDatabase = getDatabase;
21 | Logger = logger;
22 | }
23 |
24 | protected MongoDbContext GetMongoDbContext()
25 | {
26 | return new MongoDbContext(GetDatabase());
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/DatabaseContext/TeamStatsDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Components;
4 | using R5.FFDB.Core;
5 | using R5.FFDB.Core.Database;
6 | using R5.FFDB.Core.Entities;
7 | using R5.FFDB.Core.Models;
8 | using R5.FFDB.DbProviders.Mongo.Collections;
9 | using R5.FFDB.DbProviders.Mongo.Documents;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.Linq;
13 | using System.Threading.Tasks;
14 |
15 | namespace R5.FFDB.DbProviders.Mongo.DatabaseContext
16 | {
17 | public class TeamStatsDbContext : DbContextBase, ITeamStatsDbContext
18 | {
19 | public TeamStatsDbContext(
20 | Func getDatabase,
21 | IAppLogger logger)
22 | : base(getDatabase, logger)
23 | {
24 | }
25 |
26 | public async Task> GetAsync(WeekInfo week)
27 | {
28 | var collectionName = CollectionResolver.GetName();
29 |
30 | var builder = Builders.Filter;
31 | var filter = builder.Eq(s => s.Season, week.Season)
32 | & builder.Eq(s => s.Week, week.Week);
33 |
34 | List documents = await GetMongoDbContext().FindAsync(filter);
35 |
36 | Logger.LogDebug($"Retrieved team week stats for week '{week}' from '{collectionName}' collection.");
37 |
38 | return documents.Select(WeekStatsTeamDocument.ToCoreEntity).ToList();
39 | }
40 |
41 | public async Task AddAsync(List stats)
42 | {
43 | if (stats == null)
44 | {
45 | throw new ArgumentNullException(nameof(stats), "Stats must be provided.");
46 | }
47 |
48 | var collectionName = CollectionResolver.GetName();
49 |
50 | Logger.LogDebug($"Adding {stats.Count} team week stats to '{collectionName}' collection..");
51 |
52 | List documents = stats.Select(WeekStatsTeamDocument.FromCoreEntity).ToList();
53 |
54 | await GetMongoDbContext().InsertManyAsync(documents);
55 |
56 | Logger.LogDebug($"Added team week stats to '{collectionName}' collection.");
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/DatabaseContext/UpdateLogDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Components;
4 | using R5.FFDB.Core;
5 | using R5.FFDB.Core.Database;
6 | using R5.FFDB.Core.Models;
7 | using R5.FFDB.DbProviders.Mongo.Collections;
8 | using R5.FFDB.DbProviders.Mongo.Documents;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Linq;
12 | using System.Threading.Tasks;
13 |
14 | namespace R5.FFDB.DbProviders.Mongo.DatabaseContext
15 | {
16 | public class UpdateLogDbContext : DbContextBase, IUpdateLogDbContext
17 | {
18 | public UpdateLogDbContext(
19 | Func getDatabase,
20 | IAppLogger logger)
21 | : base(getDatabase, logger)
22 | {
23 | }
24 |
25 | public async Task> GetAsync()
26 | {
27 | var collectionName = CollectionResolver.GetName();
28 |
29 | var logs = await GetMongoDbContext().FindAsync();
30 |
31 | Logger.LogDebug($"Retrieved updated weeks from '{collectionName}' collection.");
32 |
33 | return logs.Select(l => new WeekInfo(l.Season, l.Week)).ToList();
34 | }
35 |
36 | public async Task AddAsync(WeekInfo week)
37 | {
38 | var collectionName = CollectionResolver.GetName();
39 |
40 | var log = new UpdateLogDocument
41 | {
42 | Season = week.Season,
43 | Week = week.Week,
44 | UpdateTime = DateTime.UtcNow
45 | };
46 |
47 | await GetMongoDbContext().InsertOneAsync(log);
48 |
49 | Logger.LogDebug($"Successfully added update log for {week} to '{collectionName}' collection.");
50 | }
51 |
52 | public async Task HasUpdatedWeekAsync(WeekInfo week)
53 | {
54 | var collectionName = CollectionResolver.GetName();
55 |
56 | var builder = Builders.Filter;
57 | var filter = builder.Eq(l => l.Season, week.Season)
58 | & builder.Eq(l => l.Week, week.Week);
59 |
60 | UpdateLogDocument log = await GetMongoDbContext().FindOneAsync(filter);
61 |
62 | return log != null;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/DatabaseContext/WeekMatchupsDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Components;
4 | using R5.FFDB.Core;
5 | using R5.FFDB.Core.Database;
6 | using R5.FFDB.Core.Entities;
7 | using R5.FFDB.Core.Models;
8 | using R5.FFDB.DbProviders.Mongo.Collections;
9 | using R5.FFDB.DbProviders.Mongo.Documents;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.Linq;
13 | using System.Threading.Tasks;
14 |
15 | namespace R5.FFDB.DbProviders.Mongo.DatabaseContext
16 | {
17 | public class WeekMatchupsDbContext : DbContextBase, IWeekMatchupsDbContext
18 | {
19 | public WeekMatchupsDbContext(
20 | Func getDatabase,
21 | IAppLogger logger)
22 | : base(getDatabase, logger)
23 | {
24 | }
25 |
26 | public async Task> GetAsync(WeekInfo week)
27 | {
28 | var collectionName = CollectionResolver.GetName();
29 |
30 | var builder = Builders.Filter;
31 | var filter = builder.Eq(s => s.Season, week.Season)
32 | & builder.Eq(s => s.Week, week.Week);
33 |
34 | List documents = await GetMongoDbContext().FindAsync(filter);
35 |
36 | Logger.LogDebug($"Retrieved week matchups for week '{week}' from '{collectionName}' collection.");
37 |
38 | return documents.Select(WeekMatchupDocument.ToCoreEntity).ToList();
39 | }
40 |
41 | public async Task AddAsync(List matchups)
42 | {
43 | if (matchups == null)
44 | {
45 | throw new ArgumentNullException(nameof(matchups), "Week matchups must be provided.");
46 | }
47 |
48 | var collectionName = CollectionResolver.GetName();
49 |
50 | Logger.LogDebug($"Adding {matchups.Count} week matchups to '{collectionName}' collection..");
51 |
52 | List documents = matchups.Select(WeekMatchupDocument.FromCoreEntity).ToList();
53 |
54 | await GetMongoDbContext().InsertManyAsync(documents);
55 |
56 | Logger.LogDebug($"Added week matchups to '{collectionName}' collection.");
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Documents/DocumentBase.cs:
--------------------------------------------------------------------------------
1 | namespace R5.FFDB.DbProviders.Mongo.Documents
2 | {
3 | public abstract class DocumentBase
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Documents/TeamDocument.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Bson.Serialization.Attributes;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Core.Entities;
4 | using R5.FFDB.DbProviders.Mongo.Collections;
5 | using System.Threading.Tasks;
6 |
7 | namespace R5.FFDB.DbProviders.Mongo.Documents
8 | {
9 | [CollectionName(Collection.Team)]
10 | public class TeamDocument : DocumentBase
11 | {
12 | [BsonId]
13 | public int Id { get; set; }
14 |
15 | [BsonElement("nflId")]
16 | public string NflId { get; set; }
17 |
18 | [BsonElement("name")]
19 | public string Name { get; set; }
20 |
21 | [BsonElement("abbreviation")]
22 | public string Abbreviation { get; set; }
23 |
24 | public static TeamDocument FromCoreEntity(Team entity)
25 | {
26 | return new TeamDocument
27 | {
28 | Id = entity.Id,
29 | NflId = entity.NflId,
30 | Name = entity.Name,
31 | Abbreviation = entity.Abbreviation
32 | };
33 | }
34 |
35 | public static Task CreateIndexAsync(IMongoDatabase database)
36 | {
37 | var keys = Builders.IndexKeys.Ascending(t => t.Id);
38 |
39 | var model = new CreateIndexModel(keys);
40 |
41 | var collection = CollectionResolver.Get(database);
42 | collection.Indexes.CreateOne(model);
43 |
44 | return collection.Indexes.CreateOneAsync(model);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Documents/UpdateLogDocument.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using MongoDB.Bson.Serialization.Attributes;
4 | using MongoDB.Driver;
5 | using R5.FFDB.DbProviders.Mongo.Collections;
6 |
7 | namespace R5.FFDB.DbProviders.Mongo.Documents
8 | {
9 | [CollectionName(Collection.UpdateLog)]
10 | public class UpdateLogDocument : DocumentBase
11 | {
12 | [BsonElement("season")]
13 | public int Season { get; set; }
14 |
15 | [BsonElement("week")]
16 | public int Week { get; set; }
17 |
18 | [BsonElement("dateTime")]
19 | public DateTimeOffset UpdateTime { get; set; }
20 |
21 | public static Task CreateIndexAsync(IMongoDatabase database)
22 | {
23 | // compound index
24 | var keys = Builders.IndexKeys
25 | .Ascending(t => t.Week)
26 | .Ascending(t => t.Season);
27 |
28 | var options = new CreateIndexOptions { Unique = true };
29 |
30 | var model = new CreateIndexModel(keys, options);
31 |
32 | var collection = CollectionResolver.Get(database);
33 |
34 | return collection.Indexes.CreateOneAsync(model);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Documents/WeekMatchupDocument.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Bson.Serialization.Attributes;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Core.Entities;
4 | using R5.FFDB.Core.Models;
5 | using R5.FFDB.DbProviders.Mongo.Collections;
6 | using System.Threading.Tasks;
7 |
8 | namespace R5.FFDB.DbProviders.Mongo.Documents
9 | {
10 | [CollectionName(Collection.WeekMatchup)]
11 | public class WeekMatchupDocument : DocumentBase
12 | {
13 | [BsonElement("season")]
14 | public int Season { get; set; }
15 |
16 | [BsonElement("week")]
17 | public int Week { get; set; }
18 |
19 | [BsonElement("homeTeamId")]
20 | public int HomeTeamId { get; set; }
21 |
22 | [BsonElement("awayTeamId")]
23 | public int AwayTeamId { get; set; }
24 |
25 | [BsonElement("nflGameId")]
26 | public string NflGameId { get; set; }
27 |
28 | [BsonElement("gsisGameId")]
29 | public string GsisGameId { get; set; }
30 |
31 | public static WeekMatchup ToCoreEntity(WeekMatchupDocument doc)
32 | {
33 | return new WeekMatchup
34 | {
35 | Week = new WeekInfo(doc.Season, doc.Week),
36 | HomeTeamId = doc.HomeTeamId,
37 | AwayTeamId = doc.AwayTeamId,
38 | NflGameId = doc.NflGameId,
39 | GsisGameId = doc.GsisGameId
40 | };
41 | }
42 |
43 | public static WeekMatchupDocument FromCoreEntity(WeekMatchup entity)
44 | {
45 | return new WeekMatchupDocument
46 | {
47 | Season = entity.Week.Season,
48 | Week = entity.Week.Week,
49 | HomeTeamId = entity.HomeTeamId,
50 | AwayTeamId = entity.AwayTeamId,
51 | NflGameId = entity.NflGameId,
52 | GsisGameId = entity.GsisGameId
53 | };
54 | }
55 |
56 | public static Task CreateIndexAsync(IMongoDatabase database)
57 | {
58 | var keys = Builders.IndexKeys
59 | .Ascending(t => t.HomeTeamId)
60 | .Ascending(t => t.AwayTeamId)
61 | .Ascending(t => t.Week)
62 | .Ascending(t => t.Season);
63 |
64 | var options = new CreateIndexOptions { Unique = true };
65 |
66 | var model = new CreateIndexModel(keys);
67 |
68 | var collection = CollectionResolver.Get(database);
69 | collection.Indexes.CreateOne(model);
70 |
71 | return collection.Indexes.CreateOneAsync(model);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Documents/WeekStatsDstDocument.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Bson.Serialization.Attributes;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Core;
4 | using R5.FFDB.Core.Entities;
5 | using R5.FFDB.Core.Models;
6 | using R5.FFDB.DbProviders.Mongo.Collections;
7 | using R5.FFDB.DbProviders.Mongo.Models;
8 | using System.Collections.Generic;
9 | using System.Threading.Tasks;
10 |
11 | namespace R5.FFDB.DbProviders.Mongo.Documents
12 | {
13 | [CollectionName(Collection.WeekStatsDst)]
14 | public class WeekStatsDstDocument : DocumentBase
15 | {
16 | [BsonElement("teamId")]
17 | public int TeamId { get; set; }
18 |
19 | [BsonElement("season")]
20 | public int Season { get; set; }
21 |
22 | [BsonElement("week")]
23 | public int Week { get; set; }
24 |
25 | [BsonElement("stats")]
26 | public Dictionary Stats { get; set; }
27 |
28 | public static WeekStatsDstDocument FromCoreEntity(PlayerWeekStats stats)
29 | {
30 | int teamId = Teams.GetIdFromNflId(stats.NflId);
31 |
32 | var dstStats = new Dictionary();
33 | foreach (KeyValuePair statKv in stats.Stats)
34 | {
35 | if (WeekStatCategory.DST.Contains(statKv.Key))
36 | {
37 | dstStats[(MongoWeekStatType)statKv.Key] = statKv.Value;
38 | }
39 | }
40 |
41 | return new WeekStatsDstDocument
42 | {
43 | TeamId = teamId,
44 | Season = stats.Week.Season,
45 | Week = stats.Week.Week,
46 | Stats = dstStats
47 | };
48 | }
49 |
50 | public static Task CreateIndexAsync(IMongoDatabase database)
51 | {
52 | // compound index
53 | var keys = Builders.IndexKeys
54 | .Ascending(t => t.TeamId)
55 | .Ascending(t => t.Week)
56 | .Ascending(t => t.Season);
57 |
58 | var options = new CreateIndexOptions { Unique = true };
59 |
60 | var model = new CreateIndexModel(keys, options);
61 |
62 | var collection = CollectionResolver.Get(database);
63 |
64 | return collection.Indexes.CreateOneAsync(model);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Models/MongoWeekStatType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.DbProviders.Mongo.Models
6 | {
7 | // keep camel-cased so it shows up correctly in mongo
8 | // mapped to Core.WeekStatType using their numeric values so
9 | // keep them the same
10 | public enum MongoWeekStatType
11 | {
12 | passAttempts = 2,
13 | passCompletions = 3,
14 | passYards = 5,
15 | passTouchdowns = 6,
16 | passInterceptions = 7,
17 | passSacked = 8,
18 |
19 | rushAttempts = 13,
20 | rushYards = 14,
21 | rushTouchdowns = 15,
22 |
23 | receiveCatches = 20,
24 | receiveYards = 21,
25 | receiveTouchdowns = 22,
26 |
27 | returnYards = 27,
28 | returnTouchdowns = 28,
29 | fumbleRecoverTouchdowns = 29,
30 | fumblesLost = 30,
31 | fumblesTotal = 31,
32 | twoPointConversions = 32,
33 |
34 | kickPatMakes = 33,
35 | kickPatMisses = 34,
36 | kickZeroTwentyMakes = 35,
37 | kickTwentyThirtyMakes = 36,
38 | kickThirtyFortyMakes = 37,
39 | kickFortyFiftyMakes = 38,
40 | kickFiftyPlusMakes = 39,
41 | kickZeroTwentyMisses = 40,
42 | kickTwentyThirtyMisses = 41,
43 | kickThirtyFortyMisses = 42,
44 | kickFortyFiftyMisses = 43,
45 | kickFiftyPlusMisses = 44,
46 |
47 | dstSacks = 45,
48 | dstInterceptions = 46,
49 | dstFumblesRecovered = 47,
50 | dstFumblesForced = 48,
51 | dstSafeties = 49,
52 | dstTouchdowns = 50,
53 | dstBlockedKicks = 51,
54 | dstReturnYards = 52,
55 | dstReturnTouchdowns = 53,
56 | dstPointsAllowed = 54,
57 | dstYardsAllowed = 62,
58 |
59 | idpTackles = 70,
60 | idpAssistedTackles = 71,
61 | idpSacks = 72,
62 | idpInterceptions = 73,
63 | idpForcedFumbles = 74,
64 | idpFumblesRecovered = 75,
65 | idpInterceptionTouchdowns = 76,
66 | idpFumbleTouchdowns = 77,
67 | idpBlockedKickTouchdowns = 78,
68 | idpBlockedKicks = 79,
69 | idpSafeties = 80,
70 | idpPassesDefended = 81,
71 | idpInterceptionReturnYards = 82,
72 | idpFumbleReturnYards = 83,
73 | idpTacklesForLoss = 84,
74 | idpQuarterBackHits = 85,
75 | idpSackYards = 86
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/MongoConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.DbProviders.Mongo
6 | {
7 | public class MongoConfig
8 | {
9 | public string ConnectionString { get; set; }
10 | public string DatabaseName { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/MongoDbProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using MongoDB.Driver;
3 | using R5.FFDB.Components;
4 | using R5.FFDB.Core;
5 | using R5.FFDB.Core.Database;
6 | using R5.FFDB.DbProviders.Mongo.DatabaseContext;
7 | using R5.FFDB.DbProviders.Mongo.Serialization;
8 |
9 | namespace R5.FFDB.DbProviders.Mongo
10 | {
11 | public class MongoDbProvider : IDatabaseProvider
12 | {
13 | private MongoConfig _config { get; }
14 | private IAppLogger _logger { get; }
15 |
16 | // use a single instance for the entire lifetime
17 | private IMongoClient _client { get; }
18 |
19 | public MongoDbProvider(
20 | MongoConfig config,
21 | IAppLogger logger)
22 | {
23 | _config = config;
24 | _logger = logger;
25 |
26 | // must be registered before first time db is used, or it'll
27 | // initialize some clashing default serializers
28 | MongoSerializers.Register();
29 |
30 | _client = new MongoClient(config.ConnectionString);
31 | }
32 |
33 | public IDatabaseContext GetContext()
34 | {
35 | return new DbContext(GetDatabase, _logger);
36 | }
37 |
38 | private IMongoDatabase GetDatabase()
39 | {
40 | return _client.GetDatabase(_config.DatabaseName);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/R5.FFDB.DbProviders.Mongo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | latest
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Serialization/MongoSerializers.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Bson;
2 | using MongoDB.Bson.Serialization;
3 | using MongoDB.Bson.Serialization.Conventions;
4 | using MongoDB.Bson.Serialization.Serializers;
5 | using R5.FFDB.Core.Models;
6 | using R5.FFDB.DbProviders.Mongo.Models;
7 | using System;
8 |
9 | namespace R5.FFDB.DbProviders.Mongo.Serialization
10 | {
11 | public static class MongoSerializers
12 | {
13 | public static void Register()
14 | {
15 | RegisterConventions();
16 |
17 | BsonSerializer.RegisterSerializer(new Serializers.GuidSerializer());
18 | BsonSerializer.RegisterSerializer(new NullableSerializer(new Serializers.GuidSerializer()));
19 | BsonSerializer.RegisterSerializer(new EnumSerializer(BsonType.String));
20 | }
21 |
22 | private static void RegisterConventions()
23 | {
24 | var cp = new ConventionPack
25 | {
26 | // ignore extra props in mongo that aren't in C# models
27 | new IgnoreExtraElementsConvention(true),
28 | new EnumRepresentationConvention(BsonType.String)
29 | };
30 |
31 | ConventionRegistry.Register("Custom Convention Pack Registries", cp, t => true);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Serialization/Serializers/DateTimeOffsetSerializer.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Bson.Serialization;
2 | using MongoDB.Bson.Serialization.Serializers;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Globalization;
6 | using System.Text;
7 |
8 | namespace R5.FFDB.DbProviders.Mongo.Serialization.Serializers
9 | {
10 | public class DateTimeOffsetSerializer : StructSerializerBase
11 | {
12 | public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTimeOffset value)
13 | {
14 | string serializedValue = value.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
15 |
16 | context.Writer.WriteString(serializedValue);
17 | }
18 |
19 | public override DateTimeOffset Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
20 | {
21 | string serializedValue = context.Reader.ReadString();
22 |
23 | return DateTimeOffset.Parse(serializedValue);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.Mongo/Serialization/Serializers/GuidSerializer.cs:
--------------------------------------------------------------------------------
1 | using MongoDB.Bson.Serialization;
2 | using MongoDB.Bson.Serialization.Serializers;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace R5.FFDB.DbProviders.Mongo.Serialization.Serializers
8 | {
9 | public class GuidSerializer : StructSerializerBase
10 | {
11 | public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Guid value)
12 | {
13 | string serializedValue = value.ToString("D");
14 |
15 | context.Writer.WriteString(serializedValue);
16 | }
17 |
18 | public override Guid Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
19 | {
20 | string serializedValue = context.Reader.ReadString();
21 |
22 | return Guid.Parse(serializedValue);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | [assembly: CLSCompliant(true)]
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseContext/DbContextBase.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Components;
3 | using R5.FFDB.Core;
4 | using R5.Internals.PostgresMapper;
5 | using System;
6 |
7 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseContext
8 | {
9 | public abstract class DbContextBase
10 | {
11 | protected DbConnection DbConnection { get; }
12 | protected IAppLogger Logger { get; }
13 |
14 | protected DbContextBase(
15 | DbConnection dbConnection,
16 | IAppLogger logger)
17 | {
18 | DbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection));
19 | Logger = logger ?? throw new ArgumentNullException(nameof(logger));
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseContext/PlayerDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Components;
3 | using R5.FFDB.Core;
4 | using R5.FFDB.Core.Database;
5 | using R5.FFDB.Core.Entities;
6 | using R5.FFDB.Core.Models;
7 | using R5.FFDB.DbProviders.PostgreSql.Entities;
8 | using R5.Internals.PostgresMapper;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Linq;
12 | using System.Threading.Tasks;
13 |
14 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseContext
15 | {
16 | public class PlayerDbContext : DbContextBase, IPlayerDbContext
17 | {
18 | public PlayerDbContext(DbConnection dbConnection, IAppLogger logger)
19 | : base(dbConnection, logger)
20 | {
21 | }
22 |
23 | public async Task> GetAllAsync()
24 | {
25 | Logger.LogDebug($"Getting all players from '{MetadataResolver.TableName()}' table.");
26 |
27 | List sqlEntries = await DbConnection.Select().ExecuteAsync();
28 |
29 | return sqlEntries.Select(PlayerSql.ToCoreEntity).ToList();
30 | }
31 |
32 | public Task AddAsync(PlayerAdd player)
33 | {
34 | if (player == null)
35 | {
36 | throw new ArgumentNullException(nameof(player), "Player add must be provided.");
37 | }
38 |
39 | Logger.LogDebug($"Adding player '{player}' to '{MetadataResolver.TableName()}' table.");
40 |
41 | PlayerSql sqlEntry = PlayerSql.FromCoreAddEntity(player);
42 |
43 | return DbConnection.Insert(sqlEntry).ExecuteAsync();
44 | }
45 |
46 | public Task UpdateAsync(Guid id, PlayerUpdate update)
47 | {
48 | if (id == Guid.Empty)
49 | {
50 | throw new ArgumentException("Player id must be provided.", nameof(id));
51 | }
52 | if (update == null)
53 | {
54 | throw new ArgumentNullException(nameof(update), "Update must be provided.");
55 | }
56 |
57 | Logger.LogDebug($"Updating player '{id}' in '{MetadataResolver.TableName()}' table.");
58 |
59 | return DbConnection.Update()
60 | .Where(p => p.Id == id)
61 | .Set(p => p.Number, update.Number)
62 | .Set(p => p.Position, update.Position)
63 | .Set(p => p.Status, update.Status)
64 | .ExecuteAsync();
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseContext/TeamDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Npgsql;
3 | using R5.FFDB.Components;
4 | using R5.FFDB.Core;
5 | using R5.FFDB.Core.Database;
6 | using R5.FFDB.Core.Entities;
7 | using R5.FFDB.Core.Models;
8 | using R5.FFDB.DbProviders.PostgreSql.Entities;
9 | using R5.Internals.PostgresMapper;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.Linq;
13 | using System.Threading.Tasks;
14 |
15 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseContext
16 | {
17 | public class TeamDbContext : DbContextBase, ITeamDbContext
18 | {
19 | public TeamDbContext(DbConnection dbConnection, IAppLogger logger)
20 | : base(dbConnection, logger)
21 | {
22 | }
23 |
24 | public async Task> GetExistingTeamIdsAsync()
25 | {
26 | List existing = await DbConnection.Select(t => t.Id).ExecuteAsync();
27 | return existing.Select(t => t.Id).ToList();
28 | }
29 |
30 | public async Task AddAsync(List teams)
31 | {
32 | if (teams == null)
33 | {
34 | throw new ArgumentNullException(nameof(teams), "Teams must be provided.");
35 | }
36 |
37 | if (!teams.Any())
38 | {
39 | return;
40 | }
41 |
42 | var sqlEntries = teams.Select(TeamSql.FromCoreEntity).ToList();
43 |
44 | Logger.LogDebug($"Adding {sqlEntries.Count} teams to '{MetadataResolver.TableName()}' table.");
45 |
46 | await DbConnection.InsertMany(sqlEntries).ExecuteAsync();
47 | }
48 |
49 | public async Task UpdateRosterMappingsAsync(List rosters)
50 | {
51 | if (rosters == null)
52 | {
53 | throw new ArgumentNullException(nameof(rosters), "Rosters must be provided.");
54 | }
55 |
56 | Logger.LogDebug($"Updating roster mappings for players in '{MetadataResolver.TableName()}'.");
57 |
58 | await DbConnection.Truncate().ExecuteAsync();
59 |
60 | List players = await DbConnection.Select(p => p.Id, p => p.NflId).ExecuteAsync();
61 | Dictionary nflIdMap = players.ToDictionary(p => p.NflId, p => p.Id);
62 |
63 | foreach (Roster roster in rosters)
64 | {
65 | await UpdateForRosterAsync(roster, nflIdMap);
66 | }
67 | }
68 |
69 |
70 | private Task UpdateForRosterAsync(Roster roster, Dictionary nflIdMap)
71 | {
72 | List entries = roster.Players
73 | .Where(p => nflIdMap.ContainsKey(p.NflId))
74 | .Select(p => PlayerTeamMapSql.ToSqlEntity(nflIdMap[p.NflId], roster.TeamId))
75 | .ToList();
76 |
77 | return DbConnection.InsertMany(entries.ToList()).ExecuteAsync();
78 | }
79 |
80 |
81 | }
82 | }
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseContext/TeamStatsDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Components;
3 | using R5.FFDB.Core;
4 | using R5.FFDB.Core.Database;
5 | using R5.FFDB.Core.Entities;
6 | using R5.FFDB.Core.Models;
7 | using R5.FFDB.DbProviders.PostgreSql.Entities;
8 | using R5.Internals.PostgresMapper;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Linq;
12 | using System.Text;
13 | using System.Threading.Tasks;
14 |
15 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseContext
16 | {
17 | public class TeamStatsDbContext : DbContextBase, ITeamStatsDbContext
18 | {
19 | public TeamStatsDbContext(DbConnection dbConnection, IAppLogger logger)
20 | : base(dbConnection, logger)
21 | {
22 | }
23 |
24 | public async Task> GetAsync(WeekInfo week)
25 | {
26 | Logger.LogDebug($"Getting team stats for '{week}' from '{MetadataResolver.TableName()}' table.");
27 |
28 | List sqlEntries = await DbConnection.Select()
29 | .Where(s => s.Season == week.Season && s.Week == week.Week)
30 | .ExecuteAsync();
31 |
32 | return sqlEntries.Select(TeamGameStatsSql.ToCoreEntity).ToList();
33 | }
34 |
35 | public Task AddAsync(List stats)
36 | {
37 | if (stats == null)
38 | {
39 | throw new ArgumentNullException(nameof(stats), "Stats must be provided.");
40 | }
41 |
42 | Logger.LogDebug($"Adding {stats.Count} team stats entries for '{stats.First().Week}' to '{MetadataResolver.TableName()}' table.");
43 |
44 | List sqlEntries = stats.Select(TeamGameStatsSql.FromCoreEntity).ToList();
45 |
46 | return DbConnection.InsertMany(sqlEntries).ExecuteAsync();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseContext/UpdateLogDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Components;
3 | using R5.FFDB.Core;
4 | using R5.FFDB.Core.Database;
5 | using R5.FFDB.Core.Models;
6 | using R5.FFDB.DbProviders.PostgreSql.Entities;
7 | using R5.Internals.PostgresMapper;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.Linq;
11 | using System.Threading.Tasks;
12 |
13 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseContext
14 | {
15 | public class UpdateLogDbContext : DbContextBase, IUpdateLogDbContext
16 | {
17 | public UpdateLogDbContext(DbConnection dbConnection, IAppLogger logger)
18 | : base(dbConnection, logger)
19 | {
20 | }
21 |
22 | public async Task> GetAsync()
23 | {
24 | List logs = await DbConnection.Select().ExecuteAsync();
25 |
26 | return logs.Select(l => new WeekInfo(l.Season, l.Week)).ToList();
27 | }
28 |
29 | public Task AddAsync(WeekInfo week)
30 | {
31 | Logger.LogDebug("Adding update log for {Week}.", week);
32 |
33 | var log = new UpdateLogSql
34 | {
35 | Season = week.Season,
36 | Week = week.Week,
37 | UpdateTime = DateTime.Now
38 | };
39 |
40 | return DbConnection.Insert(log).ExecuteAsync();
41 | }
42 |
43 | public Task HasUpdatedWeekAsync(WeekInfo week)
44 | {
45 | return DbConnection.Exists()
46 | .Where(l => l.Season == week.Season && l.Week == week.Week)
47 | .ExecuteAsync();
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseContext/WeekMatchupsDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Components;
3 | using R5.FFDB.Core;
4 | using R5.FFDB.Core.Database;
5 | using R5.FFDB.Core.Entities;
6 | using R5.FFDB.Core.Models;
7 | using R5.FFDB.DbProviders.PostgreSql.Entities;
8 | using R5.Internals.PostgresMapper;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Linq;
12 | using System.Text;
13 | using System.Threading.Tasks;
14 |
15 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseContext
16 | {
17 | public class WeekMatchupsDbContext : DbContextBase, IWeekMatchupsDbContext
18 | {
19 | public WeekMatchupsDbContext(DbConnection dbConnection, IAppLogger logger)
20 | : base(dbConnection, logger)
21 | {
22 | }
23 |
24 | public async Task> GetAsync(WeekInfo week)
25 | {
26 | Logger.LogDebug($"Retrieving week matchups for week '{week}' from '{MetadataResolver.TableName()}' table.");
27 |
28 | List matchups = await DbConnection.Select()
29 | .Where(m => m.Season == week.Season && m.Week == week.Week)
30 | .ExecuteAsync();
31 |
32 | return matchups.Select(WeekGameMatchupSql.ToCoreEntity).ToList();
33 | }
34 |
35 | public Task AddAsync(List matchups)
36 | {
37 | if (matchups == null)
38 | {
39 | throw new ArgumentNullException(nameof(matchups), "Week matchups must be provided.");
40 | }
41 |
42 | Logger.LogDebug($"Adding {matchups.Count} week matchups to '{MetadataResolver.TableName()}' table.");
43 |
44 | var sqlEntries = matchups.Select(WeekGameMatchupSql.FromCoreEntity).ToList();
45 |
46 | return DbConnection.InsertMany(sqlEntries).ExecuteAsync();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseProvider/PostgresConfig.cs:
--------------------------------------------------------------------------------
1 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseProvider
2 | {
3 | public class PostgresConfig
4 | {
5 | public string Host { get; set; }
6 | public string DatabaseName { get; set; }
7 | public string Username { get; set; }
8 | public string Password { get; set; }
9 |
10 | public bool IsSecured => !string.IsNullOrWhiteSpace(Username)
11 | && !string.IsNullOrWhiteSpace(Password);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/DatabaseProvider/PostgresDbProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Npgsql;
3 | using R5.FFDB.Components;
4 | using R5.FFDB.Core;
5 | using R5.FFDB.Core.Database;
6 | using R5.FFDB.DbProviders.PostgreSql.DatabaseContext;
7 | using R5.Internals.PostgresMapper;
8 | using System;
9 |
10 | namespace R5.FFDB.DbProviders.PostgreSql.DatabaseProvider
11 | {
12 | public class PostgresDbProvider : IDatabaseProvider
13 | {
14 | private PostgresConfig _config { get; }
15 | private IAppLogger _logger { get; }
16 |
17 | public PostgresDbProvider(
18 | PostgresConfig config,
19 | IAppLogger logger)
20 | {
21 | _config = config;
22 | _logger = logger;
23 | }
24 |
25 | public IDatabaseContext GetContext()
26 | {
27 | var dbConnection = new DbConnection(GetConnection);
28 | return new DbContext(dbConnection, _logger);
29 | }
30 |
31 | // todo: use connection builder
32 | private NpgsqlConnection GetConnection()
33 | {
34 | string connectionString = $"Host={_config.Host};Database={_config.DatabaseName};";
35 |
36 | if (_config.IsSecured)
37 | {
38 | connectionString += $"Username={_config.Username};Password={_config.Password}";
39 | }
40 |
41 | return new NpgsqlConnection(connectionString);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/PlayerSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using R5.FFDB.Core.Models;
3 | using R5.Internals.PostgresMapper.Attributes;
4 | using R5.Internals.PostgresMapper.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace R5.FFDB.DbProviders.PostgreSql.Entities
10 | {
11 | [Table(TableName.Player)]
12 | public class PlayerSql
13 | {
14 | [PrimaryKey]
15 | [Column("id", PostgresDataType.UUID)]
16 | public Guid Id { get; set; }
17 |
18 | [NotNull]
19 | [Column("nfl_id", PostgresDataType.TEXT)]
20 | public string NflId { get; set; }
21 |
22 | [Column("esb_id", PostgresDataType.TEXT)]
23 | public string EsbId { get; set; }
24 |
25 | [Column("gsis_id", PostgresDataType.TEXT)]
26 | public string GsisId { get; set; }
27 |
28 | [NotNull]
29 | [Column("first_name", PostgresDataType.TEXT)]
30 | public string FirstName { get; set; }
31 |
32 | [Column("last_name", PostgresDataType.TEXT)]
33 | public string LastName { get; set; }
34 |
35 | [Column("position", PostgresDataType.TEXT)]
36 | public Position? Position { get; set; }
37 |
38 | [Column("number", PostgresDataType.INT)]
39 | public int? Number { get; set; }
40 |
41 | [Column("status", PostgresDataType.TEXT)]
42 | public RosterStatus? Status { get; set; }
43 |
44 | [Column("height", PostgresDataType.INT)]
45 | public int Height { get; set; }
46 |
47 | [Column("weight", PostgresDataType.INT)]
48 | public int Weight { get; set; }
49 |
50 | [Column("date_of_birth", PostgresDataType.DATE)]
51 | public DateTimeOffset DateOfBirth { get; set; }
52 |
53 | [Column("college", PostgresDataType.TEXT)]
54 | public string College { get; set; }
55 |
56 | public static PlayerSql FromCoreAddEntity(PlayerAdd add)
57 | {
58 | return new PlayerSql
59 | {
60 | Id = Guid.NewGuid(),
61 | NflId = add.NflId,
62 | EsbId = add.EsbId,
63 | GsisId = add.GsisId,
64 | FirstName = add.FirstName,
65 | LastName = add.LastName,
66 | Position = add.Position,
67 | Status = add.Status,
68 | Number = add.Number,
69 | Height = add.Height,
70 | Weight = add.Weight,
71 | DateOfBirth = add.DateOfBirth,
72 | College = add.College
73 | };
74 | }
75 |
76 | public static Player ToCoreEntity(PlayerSql sql)
77 | {
78 | return new Player
79 | {
80 | Id = sql.Id,
81 | NflId = sql.NflId,
82 | EsbId = sql.EsbId,
83 | GsisId = sql.GsisId,
84 | FirstName = sql.FirstName,
85 | LastName = sql.LastName
86 | };
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/PlayerTeamMapSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using R5.FFDB.Core.Models;
3 | using R5.Internals.PostgresMapper.Attributes;
4 | using R5.Internals.PostgresMapper.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace R5.FFDB.DbProviders.PostgreSql.Entities
10 | {
11 | [Table(TableName.PlayerTeamMap)]
12 | [CompositePrimaryKeys("player_id", "team_id")]
13 | public class PlayerTeamMapSql
14 | {
15 | [NotNull]
16 | [ForeignKey(typeof(PlayerSql), "id")]
17 | [Column("player_id", PostgresDataType.UUID)]
18 | public Guid PlayerId { get; set; }
19 |
20 | [NotNull]
21 | [ForeignKey(typeof(TeamSql), "id")]
22 | [Column("team_id", PostgresDataType.INT)]
23 | public int TeamId { get; set; }
24 |
25 | public static PlayerTeamMapSql ToSqlEntity(Guid playerId, int teamId)
26 | {
27 | return new PlayerTeamMapSql
28 | {
29 | PlayerId = playerId,
30 | TeamId = teamId
31 | };
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/TeamSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using R5.Internals.PostgresMapper.Attributes;
3 | using R5.Internals.PostgresMapper.Models;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Text;
7 |
8 | namespace R5.FFDB.DbProviders.PostgreSql.Entities
9 | {
10 | [Table(TableName.Team)]
11 | public class TeamSql
12 | {
13 | [PrimaryKey]
14 | [Column("id", PostgresDataType.INT)]
15 | public int Id { get; set; }
16 |
17 | [NotNull]
18 | [Column("nfl_id", PostgresDataType.TEXT)]
19 | public string NflId { get; set; }
20 |
21 | [NotNull]
22 | [Column("name", PostgresDataType.TEXT)]
23 | public string Name { get; set; }
24 |
25 | [NotNull]
26 | [Column("abbreviation", PostgresDataType.TEXT)]
27 | public string Abbreviation { get; set; }
28 |
29 | public static TeamSql FromCoreEntity(Team entity)
30 | {
31 | return new TeamSql
32 | {
33 | Id = entity.Id,
34 | NflId = entity.NflId,
35 | Name = entity.Name,
36 | Abbreviation = entity.Abbreviation
37 | };
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/UpdateLogSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using R5.FFDB.Core.Models;
3 | using R5.Internals.PostgresMapper.Attributes;
4 | using R5.Internals.PostgresMapper.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace R5.FFDB.DbProviders.PostgreSql.Entities
10 | {
11 | [Table(TableName.UpdateLog)]
12 | [CompositePrimaryKeys("season", "week")]
13 | public class UpdateLogSql
14 | {
15 | [NotNull]
16 | [Column("season", PostgresDataType.INT)]
17 | public int Season { get; set; }
18 |
19 | [NotNull]
20 | [Column("week", PostgresDataType.INT)]
21 | public int Week { get; set; }
22 |
23 | [NotNull]
24 | [Column("datetime", PostgresDataType.TIMESTAMPTZ)]
25 | public DateTimeOffset UpdateTime { get; set; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/WeekGameMatchupSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using R5.FFDB.Core.Models;
3 | using R5.Internals.PostgresMapper.Attributes;
4 | using R5.Internals.PostgresMapper.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace R5.FFDB.DbProviders.PostgreSql.Entities
10 | {
11 | [Table(TableName.WeekGameMatchup)]
12 | [CompositePrimaryKeys("season", "week", "home_team_id", "away_team_id")]
13 | public class WeekGameMatchupSql
14 | {
15 | [NotNull]
16 | [Column("season", PostgresDataType.INT)]
17 | public int Season { get; set; }
18 |
19 | [NotNull]
20 | [Column("week", PostgresDataType.INT)]
21 | public int Week { get; set; }
22 |
23 | [NotNull]
24 | [ForeignKey(typeof(TeamSql), "id")]
25 | [Column("home_team_id", PostgresDataType.INT)]
26 | public int HomeTeamId { get; set; }
27 |
28 | [NotNull]
29 | [ForeignKey(typeof(TeamSql), "id")]
30 | [Column("away_team_id", PostgresDataType.INT)]
31 | public int AwayTeamId { get; set; }
32 |
33 | [NotNull]
34 | [Column("nfl_game_id", PostgresDataType.TEXT)]
35 | public string NflGameId { get; set; }
36 |
37 | [NotNull]
38 | [Column("gsis_game_id", PostgresDataType.TEXT)]
39 | public string GsisGameId { get; set; }
40 |
41 | public static WeekGameMatchupSql FromCoreEntity(WeekMatchup entity)
42 | {
43 | return new WeekGameMatchupSql
44 | {
45 | Season = entity.Week.Season,
46 | Week = entity.Week.Week,
47 | HomeTeamId = entity.HomeTeamId,
48 | AwayTeamId = entity.AwayTeamId,
49 | NflGameId = entity.NflGameId,
50 | GsisGameId = entity.GsisGameId
51 | };
52 | }
53 |
54 | public static WeekMatchup ToCoreEntity(WeekGameMatchupSql sql)
55 | {
56 | return new WeekMatchup
57 | {
58 | Week = new WeekInfo(sql.Season, sql.Week),
59 | HomeTeamId = sql.HomeTeamId,
60 | AwayTeamId = sql.AwayTeamId,
61 | NflGameId = sql.NflGameId,
62 | GsisGameId = sql.GsisGameId
63 | };
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/WeekStats/WeekStatsMiscSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using R5.Internals.PostgresMapper.Attributes;
5 | using R5.Internals.PostgresMapper.Models;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 |
11 | namespace R5.FFDB.DbProviders.PostgreSql.Entities.WeekStats
12 | {
13 | [Table(TableName.WeekStats.Misc)]
14 | [CompositePrimaryKeys("player_id", "season", "week")]
15 | public class WeekStatsMiscSql
16 | {
17 | [NotNull]
18 | [ForeignKey(typeof(PlayerSql), "id")]
19 | [Column("player_id", PostgresDataType.UUID)]
20 | public Guid PlayerId { get; set; }
21 |
22 | [ForeignKey(typeof(TeamSql), "id")]
23 | [Column("team_id", PostgresDataType.INT)]
24 | public int? TeamId { get; set; }
25 |
26 | [NotNull]
27 | [Column("season", PostgresDataType.INT)]
28 | public int Season { get; set; }
29 |
30 | [NotNull]
31 | [Column("week", PostgresDataType.INT)]
32 | public int Week { get; set; }
33 |
34 | [Column("fumble_recover_touchdowns", PostgresDataType.FLOAT8)]
35 | public double? FumbleRecoverTouchdowns { get; set; }
36 |
37 | [Column("fumbles_lost", PostgresDataType.FLOAT8)]
38 | public double? FumblesLost { get; set; }
39 |
40 | [Column("fumbles_total", PostgresDataType.FLOAT8)]
41 | public double? FumblesTotal { get; set; }
42 |
43 | [Column("two_point_conversions", PostgresDataType.FLOAT8)]
44 | public double? TwoPointConversions { get; set; }
45 |
46 | public void UpdateFromStats(List> stats)
47 | {
48 | foreach (KeyValuePair kv in stats)
49 | {
50 | switch (kv.Key)
51 | {
52 | case WeekStatType.Fumble_Recover_Touchdowns:
53 | this.FumbleRecoverTouchdowns = kv.Value;
54 | break;
55 | case WeekStatType.Fumbles_Lost:
56 | this.FumblesLost = kv.Value;
57 | break;
58 | case WeekStatType.Fumbles_Total:
59 | this.FumblesTotal = kv.Value;
60 | break;
61 | case WeekStatType.TwoPointConversions:
62 | this.TwoPointConversions = kv.Value;
63 | break;
64 | default:
65 | throw new ArgumentOutOfRangeException(nameof(kv.Key), $"'{kv.Key}' is either an invalid or unhandled as a misc stat type.");
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/WeekStats/WeekStatsPassSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using R5.Internals.PostgresMapper.Attributes;
5 | using R5.Internals.PostgresMapper.Models;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 |
11 | namespace R5.FFDB.DbProviders.PostgreSql.Entities.WeekStats
12 | {
13 | [Table(TableName.WeekStats.Pass)]
14 | [CompositePrimaryKeys("player_id", "season", "week")]
15 | public class WeekStatsPassSql
16 | {
17 | [NotNull]
18 | [ForeignKey(typeof(PlayerSql), "id")]
19 | [Column("player_id", PostgresDataType.UUID)]
20 | public Guid PlayerId { get; set; }
21 |
22 | [ForeignKey(typeof(TeamSql), "id")]
23 | [Column("team_id", PostgresDataType.INT)]
24 | public int? TeamId { get; set; }
25 |
26 | [NotNull]
27 | [Column("season", PostgresDataType.INT)]
28 | public int Season { get; set; }
29 |
30 | [NotNull]
31 | [Column("week", PostgresDataType.INT)]
32 | public int Week { get; set; }
33 |
34 | [Column("attempts", PostgresDataType.FLOAT8)]
35 | public double? Attempts { get; set; }
36 |
37 | [Column("completions", PostgresDataType.FLOAT8)]
38 | public double? Completions { get; set; }
39 |
40 | [Column("yards", PostgresDataType.FLOAT8)]
41 | public double? Yards { get; set; }
42 |
43 | [Column("touchdowns", PostgresDataType.FLOAT8)]
44 | public double? Touchdowns { get; set; }
45 |
46 | [Column("interceptions", PostgresDataType.FLOAT8)]
47 | public double? Interceptions { get; set; }
48 |
49 | [Column("sacked", PostgresDataType.FLOAT8)]
50 | public double? Sacked { get; set; }
51 |
52 | public void UpdateFromStats(List> stats)
53 | {
54 | foreach (KeyValuePair kv in stats)
55 | {
56 | switch (kv.Key)
57 | {
58 | case WeekStatType.Pass_Attempts:
59 | this.Attempts = kv.Value;
60 | break;
61 | case WeekStatType.Pass_Completions:
62 | this.Completions = kv.Value;
63 | break;
64 | case WeekStatType.Pass_Yards:
65 | this.Yards = kv.Value;
66 | break;
67 | case WeekStatType.Pass_Touchdowns:
68 | this.Touchdowns = kv.Value;
69 | break;
70 | case WeekStatType.Pass_Interceptions:
71 | this.Interceptions = kv.Value;
72 | break;
73 | case WeekStatType.Pass_Sacked:
74 | this.Sacked = kv.Value;
75 | break;
76 | default:
77 | throw new ArgumentOutOfRangeException(nameof(kv.Key), $"'{kv.Key}' is either an invalid or unhandled as a passing stat type.");
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/WeekStats/WeekStatsReceiveSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using R5.Internals.PostgresMapper.Attributes;
5 | using R5.Internals.PostgresMapper.Models;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 |
11 | namespace R5.FFDB.DbProviders.PostgreSql.Entities.WeekStats
12 | {
13 | [Table(TableName.WeekStats.Receive)]
14 | [CompositePrimaryKeys("player_id", "season", "week")]
15 | public class WeekStatsReceiveSql
16 | {
17 | [NotNull]
18 | [ForeignKey(typeof(PlayerSql), "id")]
19 | [Column("player_id", PostgresDataType.UUID)]
20 | public Guid PlayerId { get; set; }
21 |
22 | [ForeignKey(typeof(TeamSql), "id")]
23 | [Column("team_id", PostgresDataType.INT)]
24 | public int? TeamId { get; set; }
25 |
26 | [NotNull]
27 | [Column("season", PostgresDataType.INT)]
28 | public int Season { get; set; }
29 |
30 | [NotNull]
31 | [Column("week", PostgresDataType.INT)]
32 | public int Week { get; set; }
33 |
34 | [Column("catches", PostgresDataType.FLOAT8)]
35 | public double? Catches { get; set; }
36 |
37 | [Column("yards", PostgresDataType.FLOAT8)]
38 | public double? Yards { get; set; }
39 |
40 | [Column("touchdowns", PostgresDataType.FLOAT8)]
41 | public double? Touchdowns { get; set; }
42 |
43 | public void UpdateFromStats(List> stats)
44 | {
45 | foreach (KeyValuePair kv in stats)
46 | {
47 | switch (kv.Key)
48 | {
49 | case WeekStatType.Receive_Catches:
50 | this.Catches = kv.Value;
51 | break;
52 | case WeekStatType.Receive_Yards:
53 | this.Yards = kv.Value;
54 | break;
55 | case WeekStatType.Receive_Touchdowns:
56 | this.Touchdowns = kv.Value;
57 | break;
58 | default:
59 | throw new ArgumentOutOfRangeException(nameof(kv.Key), $"'{kv.Key}' is either an invalid or unhandled as a receiving stat type.");
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/WeekStats/WeekStatsReturnSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using R5.Internals.PostgresMapper.Attributes;
5 | using R5.Internals.PostgresMapper.Models;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 |
11 | namespace R5.FFDB.DbProviders.PostgreSql.Entities.WeekStats
12 | {
13 | [Table(TableName.WeekStats.Return)]
14 | [CompositePrimaryKeys("player_id", "season", "week")]
15 | public class WeekStatsReturnSql
16 | {
17 | [NotNull]
18 | [ForeignKey(typeof(PlayerSql), "id")]
19 | [Column("player_id", PostgresDataType.UUID)]
20 | public Guid PlayerId { get; set; }
21 |
22 | [ForeignKey(typeof(TeamSql), "id")]
23 | [Column("team_id", PostgresDataType.INT)]
24 | public int? TeamId { get; set; }
25 |
26 | [NotNull]
27 | [Column("season", PostgresDataType.INT)]
28 | public int Season { get; set; }
29 |
30 | [NotNull]
31 | [Column("week", PostgresDataType.INT)]
32 | public int Week { get; set; }
33 |
34 | [Column("yards", PostgresDataType.FLOAT8)]
35 | public double? Yards { get; set; }
36 |
37 | [Column("touchdowns", PostgresDataType.FLOAT8)]
38 | public double? Touchdowns { get; set; }
39 |
40 | public void UpdateFromStats(List> stats)
41 | {
42 | foreach (KeyValuePair kv in stats)
43 | {
44 | switch (kv.Key)
45 | {
46 | case WeekStatType.Return_Yards:
47 | this.Yards = kv.Value;
48 | break;
49 | case WeekStatType.Return_Touchdowns:
50 | this.Touchdowns = kv.Value;
51 | break;
52 | default:
53 | throw new ArgumentOutOfRangeException(nameof(kv.Key), $"'{kv.Key}' is either an invalid or unhandled as a return stat type.");
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/Entities/WeekStats/WeekStatsRushSql.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using R5.Internals.PostgresMapper.Attributes;
5 | using R5.Internals.PostgresMapper.Models;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 |
11 | namespace R5.FFDB.DbProviders.PostgreSql.Entities.WeekStats
12 | {
13 | [Table(TableName.WeekStats.Rush)]
14 | [CompositePrimaryKeys("player_id", "season", "week")]
15 | public class WeekStatsRushSql
16 | {
17 | [NotNull]
18 | [ForeignKey(typeof(PlayerSql), "id")]
19 | [Column("player_id", PostgresDataType.UUID)]
20 | public Guid PlayerId { get; set; }
21 |
22 | [ForeignKey(typeof(TeamSql), "id")]
23 | [Column("team_id", PostgresDataType.INT)]
24 | public int? TeamId { get; set; }
25 |
26 | [NotNull]
27 | [Column("season", PostgresDataType.INT)]
28 | public int Season { get; set; }
29 |
30 | [NotNull]
31 | [Column("week", PostgresDataType.INT)]
32 | public int Week { get; set; }
33 |
34 | [Column("attempts", PostgresDataType.FLOAT8)]
35 | public double? Attempts { get; set; }
36 |
37 | [Column("yards", PostgresDataType.FLOAT8)]
38 | public double? Yards { get; set; }
39 |
40 | [Column("touchdowns", PostgresDataType.FLOAT8)]
41 | public double? Touchdowns { get; set; }
42 |
43 | public void UpdateFromStats(List> stats)
44 | {
45 | foreach (KeyValuePair kv in stats)
46 | {
47 | switch (kv.Key)
48 | {
49 | case WeekStatType.Rush_Attempts:
50 | this.Attempts = kv.Value;
51 | break;
52 | case WeekStatType.Rush_Yards:
53 | this.Yards = kv.Value;
54 | break;
55 | case WeekStatType.Rush_Touchdowns:
56 | this.Touchdowns = kv.Value;
57 | break;
58 | default:
59 | throw new ArgumentOutOfRangeException(nameof(kv.Key), $"'{kv.Key}' is either an invalid or unhandled as a rushing stat type.");
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/R5.FFDB.DbProviders.PostgreSql.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | latest
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/DatabaseProviders/R5.FFDB.DbProviders.PostgreSql/TableNames.cs:
--------------------------------------------------------------------------------
1 | namespace R5.FFDB.DbProviders.PostgreSql
2 | {
3 | public static class TableName
4 | {
5 | public const string Player = "ffdb.player";
6 | public const string PlayerTeamMap = "ffdb.player_team_map";
7 | public const string TeamGameStats = "ffdb.team_game_stats";
8 | public const string Team = "ffdb.team";
9 | public const string UpdateLog = "ffdb.update_log";
10 | public const string WeekGameMatchup = "ffdb.week_game_matchup";
11 |
12 | public static class WeekStats
13 | {
14 | public const string DST = "ffdb.week_stats_dst";
15 | public const string IDP = "ffdb.week_stats_idp";
16 | public const string Kick = "ffdb.week_stats_kick";
17 | public const string Misc = "ffdb.week_stats_misc";
18 | public const string Pass = "ffdb.week_stats_pass";
19 | public const string Receive = "ffdb.week_stats_receive";
20 | public const string Rush = "ffdb.week_stats_rush";
21 | public const string Return = "ffdb.week_stats_return";
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/DevTester/DevTestServiceProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using R5.FFDB.Components.Configurations;
3 | using R5.FFDB.Components.Http;
4 | using R5.FFDB.DbProviders.PostgreSql;
5 | using R5.FFDB.DbProviders.PostgreSql.DatabaseProvider;
6 | using R5.FFDB.Engine;
7 | using Serilog;
8 | using Serilog.Events;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Text;
12 |
13 | namespace DevTester
14 | {
15 | public static class DevTestServiceProvider
16 | {
17 | public static IServiceProvider Build()
18 | {
19 | var webRequestHeaders = new Dictionary
20 | {
21 | { "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36" }
22 | };
23 | var webRequestConfig = new WebRequestConfig(1000, null, webRequestHeaders);
24 |
25 | var loggingConfig = new LoggingConfig(
26 | @"D:\Repos\ffdb_data_3\dev_test_logs\",
27 | maxBytes: null,
28 | RollingInterval.Day,
29 | rollOnFileSizeLimit: false,
30 | useDebugLogLevel: true,
31 | messageTemplate: @"{Timestamp:MM-dd HH:mm:ss} [{PipelineStage}] {Message:lj}{NewLine}{Exception}");
32 |
33 | var postgresConfig = new PostgresConfig
34 | {
35 | DatabaseName = "ffdb_test_2",
36 | Host = "localhost",
37 | Username = "ffdb",
38 | Password = "welc0me!"
39 | };
40 |
41 | var programOptions = new ProgramOptions
42 | {
43 | SaveToDisk = true,
44 | SkipRosterFetch = true,
45 | SaveOriginalSourceFiles = true
46 | };
47 |
48 | var baseServiceCollection = new EngineBaseServiceCollection();
49 |
50 | ServiceCollection services = baseServiceCollection
51 | .SetRootDataPath(@"D:\Repos\ffdb_data_4\")
52 | .SetWebRequestConfig(webRequestConfig)
53 | .SetLoggingConfig(loggingConfig)
54 | .SetDatabaseProviderFactory(loggerFactory => new PostgresDbProvider(postgresConfig, loggerFactory))
55 | .SetProgramOptions(programOptions)
56 | .Create();
57 |
58 | return services.BuildServiceProvider();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/DevTester/DevTester.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.2
6 | latest
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/DevTester/Timer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Text;
5 |
6 | namespace DevTester
7 | {
8 | public static class Timer
9 | {
10 | public static T Time(string operation, Func func, TimerUnit? timerUnit = null)
11 | {
12 | var sw = new Stopwatch();
13 | sw.Start();
14 |
15 | T t = func();
16 |
17 | sw.Stop();
18 |
19 | Timer.LogElapsedTime(sw, operation, timerUnit);
20 |
21 | return t;
22 | }
23 |
24 | public static void Time(string operation, Action action, TimerUnit? timerUnit = null)
25 | {
26 | var sw = new Stopwatch();
27 | sw.Start();
28 |
29 | action();
30 |
31 | sw.Stop();
32 |
33 | Timer.LogElapsedTime(sw, operation, timerUnit);
34 | }
35 |
36 | private static void LogElapsedTime(Stopwatch sw, string operation, TimerUnit? timerUnit)
37 | {
38 | long ms = sw.ElapsedMilliseconds;
39 | string log = $"Operation '{operation}' took ";
40 |
41 | switch (timerUnit)
42 | {
43 | case TimerUnit.Milliseconds:
44 | case null:
45 | log += $"{ms}ms.";
46 | break;
47 | case TimerUnit.Seconds:
48 | log += $"{TimeSpan.FromMilliseconds(ms).TotalSeconds}sec.";
49 | break;
50 | case TimerUnit.Minutes:
51 | log += $"{TimeSpan.FromMilliseconds(ms).TotalMinutes}min.";
52 | break;
53 | case TimerUnit.Hours:
54 | log += $"{TimeSpan.FromMilliseconds(ms).TotalHours}hr.";
55 | break;
56 | case TimerUnit.Days:
57 | log += $"{TimeSpan.FromMilliseconds(ms).TotalDays}days.";
58 | break;
59 | default:
60 | throw new ArgumentOutOfRangeException(nameof(timerUnit));
61 | }
62 |
63 | Console.ForegroundColor = ConsoleColor.Green;
64 | Console.WriteLine(log);
65 | Console.ResetColor();
66 | }
67 | }
68 |
69 | public enum TimerUnit
70 | {
71 | Milliseconds,
72 | Seconds,
73 | Minutes,
74 | Hours,
75 | Days
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Documentation/EngineDataFlow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rushfive/FFDB/3151c50943f425849a9e655acd64545717f6a8d8/Documentation/EngineDataFlow.png
--------------------------------------------------------------------------------
/Documentation/ffdb_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rushfive/FFDB/3151c50943f425849a9e655acd64545717f6a8d8/Documentation/ffdb_logo.png
--------------------------------------------------------------------------------
/Documentation/ffdb_logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rushfive/FFDB/3151c50943f425849a9e655acd64545717f6a8d8/Documentation/ffdb_logo.psd
--------------------------------------------------------------------------------
/Documentation/flow_diagram_readme.txt:
--------------------------------------------------------------------------------
1 | created using https://www.draw.io/
2 | file: ffdb-engine_core-data-source-flow.drawio
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Configurations/LoggingConfig.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 | using Serilog.Events;
3 |
4 | namespace R5.FFDB.Components.Configurations
5 | {
6 | public class LoggingConfig
7 | {
8 | public string LogDirectory { get; }
9 | public long? MaxBytes { get; }
10 | public RollingInterval RollingInterval { get; }
11 | public bool RollOnFileSizeLimit { get; }
12 | public bool UseDebugLogLevel { get; }
13 | public Microsoft.Extensions.Logging.ILogger CustomLogger { get; }
14 | public string MessageTemplate { get; }
15 |
16 | public bool IsConfigured => !string.IsNullOrWhiteSpace(LogDirectory);
17 |
18 | public LogEventLevel LogLevel => UseDebugLogLevel
19 | ? LogEventLevel.Debug
20 | : LogEventLevel.Information;
21 |
22 | public LoggingConfig(
23 | string logDirectory,
24 | long? maxBytes,
25 | RollingInterval rollingInterval,
26 | bool rollOnFileSizeLimit,
27 | bool useDebugLogLevel,
28 | string messageTemplate)
29 | {
30 | LogDirectory = logDirectory;
31 | MaxBytes = maxBytes;
32 | RollingInterval = rollingInterval;
33 | RollOnFileSizeLimit = rollOnFileSizeLimit;
34 | UseDebugLogLevel = useDebugLogLevel;
35 | MessageTemplate = messageTemplate;
36 | }
37 |
38 | private LoggingConfig(Microsoft.Extensions.Logging.ILogger logger)
39 | {
40 | CustomLogger = logger;
41 | }
42 |
43 | public static LoggingConfig Custom(Microsoft.Extensions.Logging.ILogger logger)
44 | {
45 | return new LoggingConfig(logger);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Configurations/ProgramOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.Components.Configurations
6 | {
7 | public class ProgramOptions
8 | {
9 | public bool SkipRosterFetch { get; set; }
10 | public bool SaveToDisk { get; set; }
11 | public bool SaveOriginalSourceFiles { get; set; }
12 | public bool DataRepoEnabled { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Configurations/WebRequestConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace R5.FFDB.Components.Configurations
4 | {
5 | public class WebRequestConfig
6 | {
7 | public int ThrottleMilliseconds { get; }
8 | public (int min, int max)? RandomizedThrottle { get; }
9 | public Dictionary Headers { get; }
10 |
11 | public WebRequestConfig(
12 | int throttleMilliseconds,
13 | (int min, int max)? randomizedThrottle,
14 | Dictionary headers)
15 | {
16 | ThrottleMilliseconds = throttleMilliseconds;
17 | RandomizedThrottle = randomizedThrottle;
18 | Headers = headers;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Dynamic/Rosters/Models/RosterCacheData.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using R5.FFDB.Core.Models;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | namespace R5.FFDB.Components.CoreData.Dynamic.Rosters
9 | {
10 | public class RosterCacheData
11 | {
12 | private List _rosters { get; } = new List();
13 |
14 | private Dictionary _playerDataMap { get; }
15 | = new Dictionary(StringComparer.OrdinalIgnoreCase);
16 |
17 | public void UpdateWith(Roster roster)
18 | {
19 | _rosters.Add(roster);
20 |
21 | foreach (var p in roster.Players)
22 | {
23 | _playerDataMap[p.NflId] = (p.Number, p.Position, p.Status);
24 | }
25 | }
26 |
27 | public List GetRosters()
28 | {
29 | return _rosters.ToList();
30 | }
31 |
32 | public List GetCurrentlyRosteredIds()
33 | {
34 | return _rosters
35 | .SelectMany(r => r.Players)
36 | .Select(p => p.NflId)
37 | .ToList();
38 | }
39 |
40 | public (int? number, Position? position, RosterStatus? status)? GetPlayerData(string nflId)
41 | {
42 | if (_playerDataMap.TryGetValue(nflId, out (int ?, Position ?, RosterStatus ?) data))
43 | {
44 | return data;
45 | }
46 |
47 | return null;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Dynamic/Rosters/Sources/V1/Mappers/ToCoreMapper.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1.Models;
2 | using R5.FFDB.Core.Entities;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1.Mappers
9 | {
10 | public interface IToCoreMapper : IAsyncMapper { }
11 |
12 | public class ToCoreMapper : IToCoreMapper
13 | {
14 | public Task MapAsync(RosterVersioned versionedModel, Team team)
15 | {
16 | return Task.FromResult(
17 | RosterVersioned.ToCoreEntity(versionedModel));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Dynamic/Rosters/Sources/V1/Mappers/ToVersionedMapper.cs:
--------------------------------------------------------------------------------
1 | using HtmlAgilityPack;
2 | using R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1.Models;
3 | using R5.FFDB.Core;
4 | using R5.FFDB.Core.Entities;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1.Mappers
12 | {
13 | public interface IToVersionedMapper : IAsyncMapper { }
14 |
15 | public class ToVersionedModelMapper : IToVersionedMapper
16 | {
17 | private IAppLogger _logger { get; }
18 | private IRosterScraper _scraper { get; }
19 |
20 | public ToVersionedModelMapper(
21 | IAppLogger logger,
22 | IRosterScraper scraper)
23 | {
24 | _logger = logger;
25 | _scraper = scraper;
26 | }
27 |
28 | public Task MapAsync(string httpResponse, Team team)
29 | {
30 | var page = new HtmlDocument();
31 | page.LoadHtml(httpResponse);
32 |
33 | List players = _scraper.ExtractPlayers(page);
34 |
35 | var failedPlayers = players.Where(p => p.NflId == null).ToList();
36 | if (failedPlayers.Any())
37 | {
38 | _logger.LogInformation($"Failed to scrape necessary data for {failedPlayers.Count} players. "
39 | + $"Will skip adding to team '{team}' roster. Failed players:"
40 | + Environment.NewLine + "{@FailedPlayers}", failedPlayers);
41 | }
42 |
43 | return Task.FromResult(new RosterVersioned
44 | {
45 | TeamId = team.Id,
46 | TeamAbbreviation = team.Abbreviation,
47 | Players = players.Where(p => p.NflId != null).ToList()
48 | });
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Dynamic/Rosters/Sources/V1/Models/RosterVersioned.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Text;
7 | using System.Linq;
8 |
9 | namespace R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1.Models
10 | {
11 | public class RosterVersioned
12 | {
13 | [JsonProperty("teamId")]
14 | public int TeamId { get; set; }
15 |
16 | [JsonProperty("teamAbbreviation")]
17 | public string TeamAbbreviation { get; set; }
18 |
19 | [JsonProperty("players")]
20 | public List Players { get; set; }
21 |
22 | public static Roster ToCoreEntity(RosterVersioned model)
23 | {
24 | return new Roster
25 | {
26 | TeamId = model.TeamId,
27 | TeamAbbreviation = model.TeamAbbreviation,
28 | Players = model.Players.Select(Player.ToCoreEntity).ToList()
29 | };
30 | }
31 |
32 | public override string ToString()
33 | {
34 | return $"{TeamAbbreviation} Roster";
35 | }
36 |
37 | public class Player
38 | {
39 | [JsonProperty("nflId")]
40 | public string NflId { get; set; }
41 |
42 | [JsonProperty("number")]
43 | public int? Number { get; set; }
44 |
45 | [JsonProperty("position")]
46 | public Position Position { get; set; }
47 |
48 | [JsonProperty("status")]
49 | public RosterStatus Status { get; set; }
50 |
51 | public static RosterPlayer ToCoreEntity(Player model)
52 | {
53 | return new RosterPlayer
54 | {
55 | NflId = model.NflId,
56 | Number = model.Number,
57 | Position = model.Position,
58 | Status = model.Status
59 | };
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Dynamic/Rosters/Sources/V1/RosterSource.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Components.Configurations;
3 | using R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1.Mappers;
4 | using R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1.Models;
5 | using R5.FFDB.Components.Http;
6 | using R5.FFDB.Core;
7 | using R5.FFDB.Core.Database;
8 | using R5.FFDB.Core.Entities;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Text;
12 | using System.Threading.Tasks;
13 |
14 | namespace R5.FFDB.Components.CoreData.Dynamic.Rosters.Sources.V1
15 | {
16 | public interface IRosterSource : ICoreDataSource
17 | {
18 | string GetVersionedFilePath(Team team);
19 | }
20 |
21 | public class RosterSource : CoreDataSource, IRosterSource
22 | {
23 | public RosterSource(
24 | IAppLogger logger,
25 | IToVersionedMapper toVersionedMapper,
26 | IToCoreMapper toCoreMapper,
27 | ProgramOptions programOptions,
28 | DataDirectoryPath dataPath,
29 | IWebRequestClient webClient)
30 | : base(
31 | logger,
32 | toVersionedMapper,
33 | toCoreMapper,
34 | programOptions,
35 | dataPath,
36 | webClient)
37 | {
38 | }
39 |
40 | string IRosterSource.GetVersionedFilePath(Team team)
41 | {
42 | return GetVersionedFilePath(team);
43 | }
44 |
45 | protected override bool SupportsSourceFilePersistence => false;
46 | protected override bool SupportsVersionedFilePersistence => true;
47 | protected override bool SupportsDataRepoFetch => false;
48 |
49 | protected override string GetVersionedFilePath(Team team)
50 | {
51 | return DataPath.Versioned.V1.Roster(team);
52 | }
53 |
54 | protected override string GetSourceFilePath(Team team)
55 | {
56 | return null;
57 | }
58 |
59 | protected override string GetSourceUri(Team team)
60 | {
61 | return Endpoints.Page.TeamRoster(team);
62 | }
63 |
64 | protected override string GetDataRepoUri(Team key)
65 | {
66 | return null;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/PlayerStats/Sources/V1/Mappers/SourceJsonReader.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using R5.FFDB.Core.Models;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | namespace R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Mappers
9 | {
10 | // todo: unit tests for this
11 | public static class SourceJsonReader
12 | {
13 | public static JObject GetPlayersObject(string sourceJson)
14 | {
15 | JObject sourceObject = JObject.Parse(sourceJson);
16 |
17 | JObject gameObject = GetGameObject(sourceObject);
18 |
19 | var playersObject = gameObject.SelectToken("players") as JObject;
20 | if (playersObject == null)
21 | {
22 | throw new InvalidOperationException($"Failed to read the Players JObject from the week stats source response.");
23 | }
24 |
25 | return playersObject;
26 | }
27 |
28 | private static JObject GetGameObject(JObject sourceObject)
29 | {
30 | var gameObject = sourceObject.SelectToken("games")
31 | .Children().Single()
32 | .Children().Single() as JObject;
33 |
34 | if (gameObject == null)
35 | {
36 | throw new InvalidOperationException($"Failed to read the Game JObject from the week stats source response.");
37 | }
38 |
39 | return gameObject;
40 | }
41 |
42 | public static JObject GetStatsObjectForPlayer(JToken playerValue, WeekInfo week)
43 | {
44 | var playerObject = playerValue as JObject;
45 |
46 | var statsObject = playerObject?.SelectToken($"stats.week.{week.Season}.{week.Week}") as JObject;
47 | if (statsObject == null)
48 | {
49 | throw new InvalidOperationException($"Failed to read the Stats JObject from the player object.");
50 | }
51 |
52 | return statsObject;
53 | }
54 |
55 | public static Dictionary ResolveStatsMapFromObject(JObject statsObject)
56 | {
57 | var result = new Dictionary();
58 |
59 | foreach (var s in statsObject)
60 | {
61 | if (s.Key == "pts")
62 | {
63 | continue;
64 | }
65 |
66 | if (!int.TryParse(s.Key, out int statKey)
67 | || !Enum.IsDefined(typeof(WeekStatType), statKey))
68 | {
69 | continue;
70 | }
71 |
72 | string value = s.Value.ToObject();
73 |
74 | if (!string.IsNullOrWhiteSpace(value)
75 | && double.TryParse(value, out double statValue))
76 | {
77 | result.Add((WeekStatType)statKey, statValue);
78 | }
79 | }
80 |
81 | return result;
82 | }
83 |
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/PlayerStats/Sources/V1/Mappers/ToCoreMapper.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Models;
2 | using R5.FFDB.Core.Models;
3 | using R5.FFDB.Core.Entities;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Mappers
10 | {
11 | public interface IToCoreMapper : IAsyncMapper, WeekInfo> { }
12 |
13 | public class ToCoreMapper : IToCoreMapper
14 | {
15 | public Task> MapAsync(PlayerWeekStatsVersioned versionedModel, WeekInfo week)
16 | {
17 | var result = new List();
18 |
19 | foreach(var p in versionedModel.Players)
20 | {
21 | result.Add(new PlayerWeekStats
22 | {
23 | Week = week,
24 | NflId = p.NflId,
25 | Stats = p.Stats,
26 | TeamId = p.TeamId
27 | });
28 | }
29 |
30 | return Task.FromResult(result);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/PlayerStats/Sources/V1/Mappers/ToVersionedMapper.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Models;
3 | using R5.FFDB.Components.CoreData.Static.TeamStats;
4 | using R5.FFDB.Core.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Mappers
11 | {
12 | public interface IToVersionedMapper : IAsyncMapper { }
13 |
14 | public class ToVersionedMapper : IToVersionedMapper
15 | {
16 | private ITeamWeekStatsCache _teamStatsCache { get; }
17 |
18 | public ToVersionedMapper(ITeamWeekStatsCache teamStatsCache)
19 | {
20 | _teamStatsCache = teamStatsCache;
21 | }
22 |
23 | public async Task MapAsync(string httpResponse, WeekInfo week)
24 | {
25 | JObject playersObject = SourceJsonReader.GetPlayersObject(httpResponse);
26 | Dictionary playerTeamMap = await _teamStatsCache.GetPlayerTeamMapAsync(week);
27 |
28 | var players = GetPlayers(playersObject, week, playerTeamMap);
29 |
30 | return new PlayerWeekStatsVersioned
31 | {
32 | Week = week,
33 | Players = players
34 | };
35 | }
36 |
37 | private static List GetPlayers(JObject playersObject,
38 | WeekInfo week, Dictionary playerTeamMap)
39 | {
40 | var result = new List();
41 |
42 | foreach(var p in playersObject)
43 | {
44 | string nflId = p.Key;
45 |
46 | var statsObject = SourceJsonReader.GetStatsObjectForPlayer(p.Value, week);
47 | Dictionary stats = SourceJsonReader.ResolveStatsMapFromObject(statsObject);
48 |
49 | var player = new PlayerWeekStatsVersioned.Player
50 | {
51 | NflId = nflId,
52 | Stats = stats
53 | };
54 |
55 | if (playerTeamMap.TryGetValue(nflId, out int teamId))
56 | {
57 | player.TeamId = teamId;
58 | }
59 |
60 | result.Add(player);
61 | }
62 |
63 | return result;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/PlayerStats/Sources/V1/Models/PlayerWeekStatsVersioned.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using R5.FFDB.Core.Models;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Models
8 | {
9 | public class PlayerWeekStatsVersioned
10 | {
11 | [JsonProperty("week")]
12 | public WeekInfo Week { get; set; }
13 |
14 | [JsonProperty("players")]
15 | public List Players { get; set; }
16 |
17 | public class Player
18 | {
19 | [JsonProperty("nflId")]
20 | public string NflId { get; set; }
21 |
22 | [JsonProperty("stats")]
23 | public Dictionary Stats { get; set; }
24 |
25 | [JsonProperty("teamId")]
26 | public int? TeamId { get; set; }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/PlayerStats/Sources/V1/PlayerWeekStatsSource.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.Configurations;
2 | using R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Models;
3 | using R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1.Mappers;
4 | using R5.FFDB.Components.Http;
5 | using R5.FFDB.Core.Entities;
6 | using R5.FFDB.Core.Models;
7 | using System.Collections.Generic;
8 | using R5.FFDB.Core;
9 |
10 | namespace R5.FFDB.Components.CoreData.Static.PlayerStats.Sources.V1
11 | {
12 | public interface IPlayerWeekStatsSource : ICoreDataSource, WeekInfo> { }
13 |
14 | public class PlayerWeekStatsSource : CoreDataSource, WeekInfo>, IPlayerWeekStatsSource
15 | {
16 | public PlayerWeekStatsSource(
17 | IAppLogger logger,
18 | IToVersionedMapper toVersionedMapper,
19 | IToCoreMapper toCoreMapper,
20 | ProgramOptions programOptions,
21 | DataDirectoryPath dataPath,
22 | IWebRequestClient webClient)
23 | : base(
24 | logger,
25 | toVersionedMapper,
26 | toCoreMapper,
27 | programOptions,
28 | dataPath,
29 | webClient)
30 | {
31 |
32 | }
33 |
34 | protected override bool SupportsSourceFilePersistence => true;
35 | protected override bool SupportsVersionedFilePersistence => true;
36 | protected override bool SupportsDataRepoFetch => true;
37 |
38 | protected override string GetVersionedFilePath(WeekInfo week)
39 | {
40 | return DataPath.Versioned.V1.PlayerWeekStats(week);
41 | }
42 |
43 | protected override string GetSourceFilePath(WeekInfo week)
44 | {
45 | return DataPath.SourceFiles.V1.PlayerWeekStats(week);
46 | }
47 |
48 | protected override string GetSourceUri(WeekInfo week)
49 | {
50 | return Endpoints.Api.WeekStats(week);
51 | }
52 |
53 | protected override string GetDataRepoUri(WeekInfo week)
54 | {
55 | return $"https://raw.githubusercontent.com/rushfive/FFDB.Data/master/versioned/player_week_stats/{week}.json";
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/Players/Add/Sources/V1/Mappers/ToCoreMapper.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Dynamic.Rosters;
2 | using R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1.Models;
3 | using R5.FFDB.Core.Entities;
4 | using R5.FFDB.Core.Models;
5 | using System.Threading.Tasks;
6 |
7 | namespace R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1.Mappers
8 | {
9 | public interface IToCoreMapper : IAsyncMapper { }
10 |
11 | public class ToCoreMapper : IToCoreMapper
12 | {
13 | private IRosterCache _rosterCache { get; }
14 |
15 | public ToCoreMapper(IRosterCache rosterCache)
16 | {
17 | _rosterCache = rosterCache;
18 | }
19 |
20 | public async Task MapAsync(PlayerAddVersioned versioned, string nflId)
21 | {
22 | int? number = null;
23 | Position? position = null;
24 | RosterStatus? status = null;
25 |
26 | var playerData = await _rosterCache.GetPlayerDataAsync(nflId);
27 | if (playerData.HasValue)
28 | {
29 | number = playerData.Value.number;
30 | position = playerData.Value.position;
31 | status = playerData.Value.status;
32 | }
33 |
34 | return new PlayerAdd
35 | {
36 | NflId = versioned.NflId,
37 | EsbId = versioned.EsbId,
38 | GsisId = versioned.EsbId,
39 | FirstName = versioned.FirstName,
40 | LastName = versioned.LastName,
41 | Height = versioned.Height,
42 | Weight = versioned.Weight,
43 | DateOfBirth = versioned.DateOfBirth,
44 | College = versioned.College,
45 | Number = number,
46 | Position = position,
47 | Status = status
48 | };
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/Players/Add/Sources/V1/Mappers/ToVersionedMapper.cs:
--------------------------------------------------------------------------------
1 | using HtmlAgilityPack;
2 | using R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1.Models;
3 | using R5.FFDB.Core;
4 | using System;
5 | using System.Threading.Tasks;
6 |
7 | namespace R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1.Mappers
8 | {
9 | public interface IToVersionedMapper : IAsyncMapper { }
10 |
11 | public class ToVersionedMapper : IToVersionedMapper
12 | {
13 | private IAppLogger _logger { get; }
14 | private IPlayerScraper _scraper { get; }
15 |
16 | public ToVersionedMapper(
17 | IAppLogger logger,
18 | IPlayerScraper scraper)
19 | {
20 | _logger = logger;
21 | _scraper = scraper;
22 | }
23 |
24 | public Task MapAsync(string httpResponse, string nflId)
25 | {
26 | var page = new HtmlDocument();
27 | page.LoadHtml(httpResponse);
28 |
29 | (string firstName, string lastName) = _scraper.ExtractNames(page);
30 | (int height, int weight) = _scraper.ExtractHeightWeight(page);
31 | DateTimeOffset dateOfBirth = _scraper.ExtractDateOfBirth(page);
32 | string college = _scraper.ExtractCollege(page);
33 | (string esbId, string gsisId) = _scraper.ExtractIds(page);
34 |
35 | if (esbId == null || gsisId == null)
36 | {
37 | throw new SourceDataScrapeException($"Failed to scrape esbId and/or gsisId for player '{nflId}' ({firstName} {lastName}).", httpResponse);
38 | }
39 |
40 | return Task.FromResult(new PlayerAddVersioned
41 | {
42 | FirstName = firstName,
43 | LastName = lastName,
44 | NflId = nflId,
45 | EsbId = esbId,
46 | GsisId = gsisId,
47 | Height = height,
48 | Weight = weight,
49 | DateOfBirth = dateOfBirth.DateTime,
50 | College = college
51 | });
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/Players/Add/Sources/V1/Models/PlayerAddVersioned.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Text;
7 |
8 | namespace R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1.Models
9 | {
10 | public class PlayerAddVersioned
11 | {
12 | [JsonProperty("nflId")]
13 | public string NflId { get; set; }
14 |
15 | [JsonProperty("esbId")]
16 | public string EsbId { get; set; }
17 |
18 | [JsonProperty("gsisId")]
19 | public string GsisId { get; set; }
20 |
21 | [JsonProperty("firstName")]
22 | public string FirstName { get; set; }
23 |
24 | [JsonProperty("lastName")]
25 | public string LastName { get; set; }
26 |
27 | [JsonProperty("height")]
28 | public int Height { get; set; }
29 |
30 | [JsonProperty("weight")]
31 | public int Weight { get; set; }
32 |
33 | [JsonProperty("dateOfBirth")]
34 | public DateTimeOffset DateOfBirth { get; set; }
35 |
36 | [JsonProperty("college")]
37 | public string College { get; set; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/Players/Add/Sources/V1/PlayerAddSource.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Components.Configurations;
3 | using R5.FFDB.Components.CoreData.Dynamic.Rosters;
4 | using R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1.Mappers;
5 | using R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1.Models;
6 | using R5.FFDB.Components.Http;
7 | using R5.FFDB.Core;
8 | using R5.FFDB.Core.Entities;
9 |
10 | namespace R5.FFDB.Components.CoreData.Static.Players.Add.Sources.V1
11 | {
12 | public interface IPlayerAddSource : ICoreDataSource { }
13 |
14 | public class PlayerAddSource : CoreDataSource, IPlayerAddSource
15 | {
16 | private IRosterCache _rosterCache { get; }
17 |
18 | public PlayerAddSource(
19 | IRosterCache rosterCache,
20 | IAppLogger logger,
21 | IToVersionedMapper toVersionedMapper,
22 | IToCoreMapper toCoreMapper,
23 | ProgramOptions programOptions,
24 | DataDirectoryPath dataPath,
25 | IWebRequestClient webClient)
26 | : base(
27 | logger,
28 | toVersionedMapper,
29 | toCoreMapper,
30 | programOptions,
31 | dataPath,
32 | webClient)
33 | {
34 | _rosterCache = rosterCache;
35 | }
36 |
37 | protected override bool SupportsSourceFilePersistence => false;
38 | protected override bool SupportsVersionedFilePersistence => true;
39 | protected override bool SupportsDataRepoFetch => true;
40 |
41 | protected override string GetVersionedFilePath(string nflId)
42 | {
43 | return DataPath.Versioned.V1.PlayerAdd(nflId);
44 | }
45 |
46 | protected override string GetSourceFilePath(string nflId)
47 | {
48 | return DataPath.SourceFiles.V1.PlayerAdd(nflId);
49 | }
50 |
51 | protected override string GetSourceUri(string nflId)
52 | {
53 | return Endpoints.Page.PlayerProfile(nflId);
54 | }
55 |
56 | protected override string GetDataRepoUri(string nflId)
57 | {
58 | return $"https://raw.githubusercontent.com/rushfive/FFDB.Data/master/versioned/player_add/{nflId}.json";
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/Players/PlayerIdMappings.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Database;
2 | using R5.FFDB.Core.Entities;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace R5.FFDB.Components.CoreData.Static.Players
10 | {
11 | public interface IPlayerIdMappings
12 | {
13 | Task> GetGsisToNflMapAsync();
14 | Task> GetNflToIdMapAsync();
15 | }
16 |
17 | public class PlayerIdMappings : IPlayerIdMappings
18 | {
19 | private IDatabaseProvider _dbProvider { get; }
20 |
21 | public PlayerIdMappings(IDatabaseProvider dbProvider)
22 | {
23 | _dbProvider = dbProvider;
24 | }
25 |
26 | public async Task> GetGsisToNflMapAsync()
27 | {
28 | IDatabaseContext dbContext = _dbProvider.GetContext();
29 |
30 | List players = await dbContext.Player.GetAllAsync();
31 |
32 | return players.ToDictionary(p => p.GsisId, p => p.NflId);
33 | }
34 |
35 | public async Task> GetNflToIdMapAsync()
36 | {
37 | IDatabaseContext dbContext = _dbProvider.GetContext();
38 |
39 | List players = await dbContext.Player.GetAllAsync();
40 |
41 | return players.ToDictionary(p => p.NflId, p => p.Id);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/ReceiverTargets/PlayerMatcher/EditDistance.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.Components.CoreData.Static.ReceiverTargets.PlayerMatcher
6 | {
7 | public static class EditDistance
8 | {
9 | public static int Find(string s1, string s2)
10 | {
11 | var memo = new Dictionary();
12 | return FindRecurse(s1, s2, 0, 0, memo);
13 | }
14 |
15 | private static int FindRecurse(string s1, string s2,
16 | int i1, int i2, Dictionary memo)
17 | {
18 | string key = $"{i1}-{i2}";
19 |
20 | if (memo.ContainsKey(key))
21 | {
22 | return memo[key];
23 | }
24 |
25 | if (i1 == s1.Length)
26 | {
27 | return s2.Length - i2;
28 | }
29 | if (i2 == s2.Length)
30 | {
31 | return s1.Length - i1;
32 | }
33 |
34 | int minOps;
35 | if (s1[i1] == s2[i2])
36 | {
37 | minOps = FindRecurse(s1, s2, i1 + 1, i2 + 1, memo);
38 | }
39 | else
40 | {
41 | int delete = FindRecurse(s1, s2, i1 + 1, i2, memo);
42 | int insert = FindRecurse(s1, s2, i1, i2 + 1, memo);
43 | int replace = FindRecurse(s1, s2, i1 + 1, i2 + 1, memo);
44 |
45 | minOps = 1 + Math.Min(delete, Math.Min(insert, replace));
46 | }
47 |
48 | memo[key] = minOps;
49 | return minOps;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/TeamStats/Models/TeamWeekStatsCacheData.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace R5.FFDB.Components.CoreData.Static.TeamStats.Models
8 | {
9 | public class TeamWeekStatsCacheData
10 | {
11 | private List _stats { get; } = new List();
12 | private Dictionary _playerTeamMap { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
13 |
14 | public void UpdateWith(TeamWeekStats stats)
15 | {
16 | _stats.Add(stats);
17 |
18 | foreach (var id in stats.PlayerNflIds)
19 | {
20 | _playerTeamMap[id] = stats.TeamId;
21 | }
22 | }
23 |
24 | public List GetStats()
25 | {
26 | return _stats.ToList();
27 | }
28 |
29 | public Dictionary GetPlayerTeamMap()
30 | {
31 | return new Dictionary(_playerTeamMap, StringComparer.OrdinalIgnoreCase);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/TeamStats/Models/TeamWeekStatsSourceModel.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 |
3 | namespace R5.FFDB.Components.CoreData.Static.TeamStats.Models
4 | {
5 | public class TeamWeekStatsSourceModel
6 | {
7 | public TeamWeekStats HomeTeamStats { get; set; }
8 | public TeamWeekStats AwayTeamStats { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/TeamStats/Sources/V1/Mappers/ToCoreMapper.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Static.TeamStats.Models;
2 | using R5.FFDB.Components.CoreData.Static.TeamStats.Sources.V1.Models;
3 | using R5.FFDB.Core.Entities;
4 | using R5.FFDB.Core.Models;
5 | using System.Threading.Tasks;
6 |
7 | namespace R5.FFDB.Components.CoreData.Static.TeamStats.Sources.V1.Mappers
8 | {
9 | public interface IToCoreMapper : IAsyncMapper { }
10 |
11 | public class ToCoreMapper : IToCoreMapper
12 | {
13 | public Task MapAsync(TeamWeekStatsVersioned model, (string, WeekInfo) gameWeek)
14 | {
15 | var home = MapStats(model.HomeTeamStats, model.Week);
16 | var away = MapStats(model.AwayTeamStats, model.Week);
17 |
18 | return Task.FromResult(new TeamWeekStatsSourceModel
19 | {
20 | HomeTeamStats = home,
21 | AwayTeamStats = away
22 | });
23 | }
24 |
25 | private TeamWeekStats MapStats(TeamWeekStatsVersioned.Stats model, WeekInfo week)
26 | {
27 | return new TeamWeekStats
28 | {
29 | TeamId = model.Id,
30 | Week = week,
31 | PlayerNflIds = model.PlayerNflIds,
32 | PointsFirstQuarter = model.PointsFirstQuarter,
33 | PointsSecondQuarter = model.PointsSecondQuarter,
34 | PointsThirdQuarter = model.PointsThirdQuarter,
35 | PointsFourthQuarter = model.PointsFourthQuarter,
36 | PointsOverTime = model.PointsOverTime,
37 | PointsTotal = model.PointsTotal,
38 | FirstDowns = model.FirstDowns,
39 | TotalYards = model.TotalYards,
40 | PassingYards = model.PassingYards,
41 | RushingYards = model.RushingYards,
42 | Penalties = model.Penalties,
43 | PenaltyYards = model.PenaltyYards,
44 | Turnovers = model.Turnovers,
45 | Punts = model.Punts,
46 | PuntYards = model.PuntYards,
47 | TimeOfPossessionSeconds = model.TimeOfPossessionSeconds
48 | };
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/TeamStats/Sources/V1/Models/TeamWeekStatsVersioned.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using R5.FFDB.Core.Models;
3 | using System.Collections.Generic;
4 |
5 | namespace R5.FFDB.Components.CoreData.Static.TeamStats.Sources.V1.Models
6 | {
7 | public class TeamWeekStatsVersioned
8 | {
9 | [JsonProperty("week")]
10 | public WeekInfo Week { get; set; }
11 |
12 | [JsonProperty("homeTeamStats")]
13 | public Stats HomeTeamStats { get; set; }
14 |
15 | [JsonProperty("awayTeamStats")]
16 | public Stats AwayTeamStats { get; set; }
17 |
18 | public class Stats
19 | {
20 | [JsonProperty("id")]
21 | public int Id { get; set; }
22 |
23 | // list of players from team active, must map from esb to nflid
24 | // todo: better way of doing this, the DbContext providers shouldn't have
25 | // this info, its only used to populate the cache
26 | [JsonProperty("playerNflIds")]
27 | public List PlayerNflIds { get; set; }
28 |
29 | [JsonProperty("pointsFirstQuarter")]
30 | public int PointsFirstQuarter { get; set; }
31 |
32 | [JsonProperty("pointsSecondQuarter")]
33 | public int PointsSecondQuarter { get; set; }
34 |
35 | [JsonProperty("pointsThirdQuarter")]
36 | public int PointsThirdQuarter { get; set; }
37 |
38 | [JsonProperty("pointsFourthQuarter")]
39 | public int PointsFourthQuarter { get; set; }
40 |
41 | [JsonProperty("pointsOverTime")]
42 | public int PointsOverTime { get; set; }
43 |
44 | [JsonProperty("pointsTotal")]
45 | public int PointsTotal { get; set; }
46 |
47 |
48 | [JsonProperty("firstDowns")]
49 | public int FirstDowns { get; set; }
50 |
51 | [JsonProperty("totalYards")]
52 | public int TotalYards { get; set; }
53 |
54 | [JsonProperty("passingYards")]
55 | public int PassingYards { get; set; }
56 |
57 | [JsonProperty("rushingYards")]
58 | public int RushingYards { get; set; }
59 |
60 | [JsonProperty("penalties")]
61 | public int Penalties { get; set; }
62 |
63 | [JsonProperty("penaltyYards")]
64 | public int PenaltyYards { get; set; }
65 |
66 | [JsonProperty("turnovers")]
67 | public int Turnovers { get; set; }
68 |
69 | [JsonProperty("punts")]
70 | public int Punts { get; set; }
71 |
72 | [JsonProperty("puntYards")]
73 | public int PuntYards { get; set; }
74 |
75 | [JsonProperty("timeOfPossessionSeconds")]
76 | public int TimeOfPossessionSeconds { get; set; }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/TeamStats/Sources/V1/TeamWeekStatsSource.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.Configurations;
2 | using R5.FFDB.Components.CoreData.Static.TeamStats.Sources.V1.Models;
3 | using R5.FFDB.Components.CoreData.Static.TeamStats.Sources.V1.Mappers;
4 | using R5.FFDB.Components.Http;
5 | using R5.FFDB.Core.Models;
6 | using R5.FFDB.Components.CoreData.Static.TeamStats.Models;
7 | using R5.FFDB.Core;
8 |
9 | namespace R5.FFDB.Components.CoreData.Static.TeamStats.Sources.V1
10 | {
11 | public interface ITeamWeekStatsSource : ICoreDataSource { }
12 |
13 | public class TeamWeekStatsSource : CoreDataSource, ITeamWeekStatsSource
14 | {
15 | public TeamWeekStatsSource(
16 | IAppLogger logger,
17 | IToVersionedMapper toVersionedMapper,
18 | IToCoreMapper toCoreMapper,
19 | ProgramOptions programOptions,
20 | DataDirectoryPath dataPath,
21 | IWebRequestClient webClient)
22 | : base(
23 | logger,
24 | toVersionedMapper,
25 | toCoreMapper,
26 | programOptions,
27 | dataPath,
28 | webClient)
29 | {
30 |
31 | }
32 |
33 | protected override bool SupportsSourceFilePersistence => true;
34 | protected override bool SupportsVersionedFilePersistence => true;
35 | protected override bool SupportsDataRepoFetch => true;
36 |
37 | protected override string GetVersionedFilePath((string, WeekInfo) gameWeek)
38 | {
39 | return DataPath.Versioned.V1.TeamStats(gameWeek.Item1);
40 | }
41 |
42 | protected override string GetSourceFilePath((string, WeekInfo) gameWeek)
43 | {
44 | return DataPath.SourceFiles.V1.TeamStats(gameWeek.Item1);
45 | }
46 |
47 | protected override string GetSourceUri((string, WeekInfo) gameWeek)
48 | {
49 | return Endpoints.Api.GameCenterStats(gameWeek.Item1);
50 | }
51 |
52 | protected override string GetDataRepoUri((string, WeekInfo) gameWeek)
53 | {
54 | return $"https://raw.githubusercontent.com/rushfive/FFDB.Data/master/versioned/team_stats/{gameWeek.Item1}.json";
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/TeamStats/TeamWeekStatsCache.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Static.TeamStats.Models;
2 | using R5.FFDB.Components.CoreData.Static.TeamStats.Sources.V1;
3 | using R5.FFDB.Components.CoreData.Static.WeekMatchups;
4 | using R5.FFDB.Core;
5 | using R5.FFDB.Core.Entities;
6 | using R5.FFDB.Core.Models;
7 | using R5.Internals.Caching.Caches;
8 | using System.Collections.Generic;
9 | using System.Threading.Tasks;
10 |
11 | namespace R5.FFDB.Components.CoreData.Static.TeamStats
12 | {
13 | public interface ITeamWeekStatsCache
14 | {
15 | Task> GetForWeekAsync(WeekInfo week);
16 | Task> GetPlayerTeamMapAsync(WeekInfo week);
17 | }
18 |
19 | public class TeamWeekStatsCache : ITeamWeekStatsCache
20 | {
21 | public static string CacheKey(WeekInfo week) => $"team_week_stats_{week}";
22 |
23 | private IAppLogger _logger { get; }
24 | private IAsyncLazyCache _cache { get; }
25 | private ITeamWeekStatsSource _source { get; }
26 | private IWeekMatchupsCache _weekMatchups { get; }
27 |
28 | public TeamWeekStatsCache(
29 | IAppLogger logger,
30 | IAsyncLazyCache cache,
31 | ITeamWeekStatsSource source,
32 | IWeekMatchupsCache weekMatchups)
33 | {
34 | _logger = logger;
35 | _cache = cache;
36 | _source = source;
37 | _weekMatchups = weekMatchups;
38 | }
39 |
40 | public async Task> GetForWeekAsync(WeekInfo week)
41 | {
42 | TeamWeekStatsCacheData cacheData = await _cache.GetOrCreateAsync(CacheKey(week), () => CreateCacheDataAsync(week));
43 |
44 | return cacheData.GetStats();
45 | }
46 |
47 | public async Task> GetPlayerTeamMapAsync(WeekInfo week)
48 | {
49 | TeamWeekStatsCacheData cacheData = await _cache.GetOrCreateAsync(CacheKey(week), () => CreateCacheDataAsync(week));
50 |
51 | return cacheData.GetPlayerTeamMap();
52 | }
53 |
54 | private async Task CreateCacheDataAsync(WeekInfo week)
55 | {
56 | var data = new TeamWeekStatsCacheData();
57 |
58 | _logger.LogInformation($"Resolving team week stats for week '{week}'.");
59 |
60 | List gameIds = await _weekMatchups.GetGameIdsForWeekAsync(week);
61 | foreach(var id in gameIds)
62 | {
63 | SourceResult result = await _source.GetAsync((id, week));
64 | data.UpdateWith(result.Value.HomeTeamStats);
65 | data.UpdateWith(result.Value.AwayTeamStats);
66 |
67 | _logger.LogDebug($"Resolved team week stats for game '{id}'.");
68 | }
69 |
70 | return data;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/WeekMatchups/Sources/V1/Mappers/ToCoreMapper.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1.Models;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 |
7 | namespace R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1.Mappers
8 | {
9 | public interface IToCoreMapper : IAsyncMapper, WeekInfo> { }
10 |
11 | public class ToCoreMapper : IToCoreMapper
12 | {
13 | public Task> MapAsync(WeekMatchupsVersioned versionedModel, WeekInfo week)
14 | {
15 | var result = new List();
16 |
17 | foreach (WeekMatchupsVersioned.Game game in versionedModel.Games)
18 | {
19 | result.Add(new WeekMatchup
20 | {
21 | Week = versionedModel.Week,
22 | HomeTeamId = game.HomeTeamId,
23 | AwayTeamId = game.AwayTeamId,
24 | NflGameId = game.NflGameId,
25 | GsisGameId = game.GsisGameId
26 | });
27 | }
28 |
29 | return Task.FromResult(result);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/WeekMatchups/Sources/V1/Mappers/ToVersionedMapper.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1.Models;
2 | using R5.FFDB.Core;
3 | using R5.FFDB.Core.Models;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 | using System.Xml.Linq;
9 |
10 | namespace R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1.Mappers
11 | {
12 | // parses XML response from NFLs score strip endpoint:
13 | // http://www.nfl.com/ajax/scorestrip?season={season}&seasonType=REG&week={week}
14 |
15 | public interface IToVersionedMapper : IAsyncMapper { }
16 |
17 | public class ToVersionedMapper : IToVersionedMapper
18 | {
19 | public Task MapAsync(string httpResponse, WeekInfo week)
20 | {
21 | XElement weekGameXml = XElement.Parse(httpResponse);
22 |
23 | XElement gamesNode = weekGameXml.Elements("gms").Single();
24 |
25 | var model = new WeekMatchupsVersioned
26 | {
27 | Week = week,
28 | Games = new List()
29 | };
30 |
31 | foreach (XElement game in gamesNode.Elements("g"))
32 | {
33 | int homeTeamId = Teams.GetIdFromAbbreviation(game.Attribute("h").Value, includePriorLookup: true);
34 | int awayTeamId = Teams.GetIdFromAbbreviation(game.Attribute("v").Value, includePriorLookup: true);
35 | string nflGameId = game.Attribute("eid").Value;
36 | string gsisGameId = game.Attribute("gsis").Value;
37 |
38 | var matchup = new WeekMatchupsVersioned.Game
39 | {
40 | HomeTeamId = homeTeamId,
41 | AwayTeamId = awayTeamId,
42 | NflGameId = nflGameId,
43 | GsisGameId = gsisGameId
44 | };
45 |
46 | model.Games.Add(matchup);
47 | }
48 |
49 | return Task.FromResult(model);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/WeekMatchups/Sources/V1/Models/WeekMatchupsVersioned.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using R5.FFDB.Core.Models;
3 | using System.Collections.Generic;
4 |
5 | namespace R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1.Models
6 | {
7 | public class WeekMatchupsVersioned
8 | {
9 | [JsonProperty("week")]
10 | public WeekInfo Week { get; set; }
11 |
12 | [JsonProperty("games")]
13 | public List Games { get; set; }
14 |
15 | public class Game
16 | {
17 | [JsonProperty("nflGameId")]
18 | public string NflGameId { get; set; }
19 |
20 | [JsonProperty("gsisGameId")]
21 | public string GsisGameId { get; set; }
22 |
23 | [JsonProperty("homeTeamId")]
24 | public int HomeTeamId { get; set; }
25 |
26 | [JsonProperty("awayTeamId")]
27 | public int AwayTeamId { get; set; }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/WeekMatchups/Sources/V1/WeekMatchupSource.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.Configurations;
2 | using R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1.Mappers;
3 | using R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1.Models;
4 | using R5.FFDB.Components.Http;
5 | using R5.FFDB.Core;
6 | using R5.FFDB.Core.Entities;
7 | using R5.FFDB.Core.Models;
8 | using System.Collections.Generic;
9 |
10 | namespace R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1
11 | {
12 | public interface IWeekMatchupSource : ICoreDataSource, WeekInfo> { }
13 |
14 | public class WeekMatchupSource : CoreDataSource, WeekInfo>, IWeekMatchupSource
15 | {
16 | public WeekMatchupSource(
17 | IAppLogger logger,
18 | IToVersionedMapper toVersionedMapper,
19 | IToCoreMapper toCoreMapper,
20 | ProgramOptions programOptions,
21 | DataDirectoryPath dataPath,
22 | IWebRequestClient webClient)
23 | : base(
24 | logger,
25 | toVersionedMapper,
26 | toCoreMapper,
27 | programOptions,
28 | dataPath,
29 | webClient)
30 | { }
31 |
32 | protected override bool SupportsSourceFilePersistence => true;
33 | protected override bool SupportsVersionedFilePersistence => true;
34 | protected override bool SupportsDataRepoFetch => true;
35 |
36 | protected override string GetVersionedFilePath(WeekInfo week)
37 | {
38 | return DataPath.Versioned.V1.WeekMatchup(week);
39 | }
40 |
41 | protected override string GetSourceFilePath(WeekInfo week)
42 | {
43 | return DataPath.SourceFiles.V1.WeekMatchup(week);
44 | }
45 |
46 | protected override string GetSourceUri(WeekInfo week)
47 | {
48 | return Endpoints.Api.ScoreStripWeekGames(week);
49 | }
50 |
51 | protected override string GetDataRepoUri(WeekInfo key)
52 | {
53 | return $"https://raw.githubusercontent.com/rushfive/FFDB.Data/master/versioned/week_matchup/{key}.json";
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/CoreData/Static/WeekMatchups/WeekMatchupsCache.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.CoreData.Static.WeekMatchups.Sources.V1;
2 | using R5.FFDB.Core.Entities;
3 | using R5.FFDB.Core.Models;
4 | using R5.Internals.Caching.Caches;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace R5.FFDB.Components.CoreData.Static.WeekMatchups
10 | {
11 | public interface IWeekMatchupsCache
12 | {
13 | Task> GetMatchupsForWeekAsync(WeekInfo week);
14 | Task> GetGameIdsForWeekAsync(WeekInfo week);
15 | }
16 |
17 | public class WeekMatchupsCache : IWeekMatchupsCache
18 | {
19 | public static string CacheKey(WeekInfo week) => $"week_matchups_{week}";
20 |
21 | private IAsyncLazyCache _cache { get; }
22 | private IWeekMatchupSource _source { get; }
23 |
24 | public WeekMatchupsCache(
25 | IAsyncLazyCache cache,
26 | IWeekMatchupSource source)
27 | {
28 | _cache = cache;
29 | _source = source;
30 | }
31 |
32 | public async Task> GetMatchupsForWeekAsync(WeekInfo week)
33 | {
34 | SourceResult> result = await _cache.GetOrCreateAsync(CacheKey(week), () => _source.GetAsync(week));
35 |
36 | return result.Value;
37 | }
38 |
39 | public async Task> GetGameIdsForWeekAsync(WeekInfo week)
40 | {
41 | SourceResult> result = await _cache.GetOrCreateAsync(CacheKey(week), () => _source.GetAsync(week));
42 |
43 | return result.Value.Select(m => m.NflGameId).ToList();
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Exceptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.Components
6 | {
7 | public class SourceDataScrapeException : Exception
8 | {
9 | private string _sourceData { get; }
10 |
11 | public SourceDataScrapeException(string message, string sourceData)
12 | : base(message)
13 | {
14 | _sourceData = sourceData;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Extensions/JsonConverters/WeekInfoJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using R5.FFDB.Core.Models;
3 | using System;
4 |
5 | namespace R5.FFDB.Components.Extensions.JsonConverters
6 | {
7 | public class WeekInfoJsonConverter : JsonConverter
8 | {
9 | public override WeekInfo ReadJson(JsonReader reader, Type objectType, WeekInfo existingValue, bool hasExistingValue, JsonSerializer serializer)
10 | {
11 | if (reader.Value == null || reader.TokenType == JsonToken.Null)
12 | {
13 | throw new InvalidOperationException($"A serialized '{nameof(WeekInfo)}' value should never be null.");
14 | }
15 |
16 | var (season, week) = ParseSerializedValue((string)reader.Value);
17 | return new WeekInfo(season, week);
18 | }
19 |
20 | private static (int season, int week) ParseSerializedValue(string serialized)
21 | {
22 | if (string.IsNullOrWhiteSpace(serialized))
23 | {
24 | throw new ArgumentException($"'{nameof(WeekInfo)}' value must be provided but was null or empty.");
25 | }
26 |
27 | string[] split = serialized.Split('-');
28 | if (split.Length != 2)
29 | {
30 | throw new ArgumentException($"Failed to split '{serialized}' into season and week parts.");
31 | }
32 |
33 | if (!int.TryParse(split[0], out int season))
34 | {
35 | throw new ArgumentException($"The serialized value '{serialized}' contains an invalid season: '{split[0]}'");
36 | }
37 |
38 | if (!int.TryParse(split[1], out int week))
39 | {
40 | throw new ArgumentException($"The serialized value '{serialized}' contains an invalid week: '{split[1]}'");
41 | }
42 |
43 | return (season, week);
44 | }
45 |
46 | public override void WriteJson(JsonWriter writer, WeekInfo value, JsonSerializer serializer)
47 | {
48 | string serialized = $"{value.Season}-{value.Week}";
49 | writer.WriteValue(serialized);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Http/Endpoints.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Entities;
2 | using R5.FFDB.Core.Models;
3 |
4 | namespace R5.FFDB.Components.Http
5 | {
6 | public static class Endpoints
7 | {
8 | public static class Api
9 | {
10 | // json
11 | public static string WeekStats(WeekInfo week)
12 | {
13 | return $"http://api.fantasy.nfl.com/v2/players/weekstats?season={week.Season}&week={week.Week}";
14 | }
15 |
16 | // xml
17 | public static string ScoreStripWeekGames(WeekInfo week)
18 | {
19 | return $"http://www.nfl.com/ajax/scorestrip?season={week.Season}&seasonType=REG&week={week.Week}";
20 | }
21 |
22 | // json
23 | public static string GameCenterStats(string gameId)
24 | {
25 | return $"http://www.nfl.com/liveupdate/game-center/{gameId}/{gameId}_gtd.json";
26 | }
27 | }
28 |
29 | public static class Page
30 | {
31 | public static string PlayerProfile(string nflId)
32 | {
33 | return $"http://www.nfl.com/player/{nflId}/{nflId}/profile";
34 | }
35 |
36 | public static string TeamRoster(Team team)
37 | {
38 | return $"http://www.nfl.com/teams/{team.ShortName}/roster?team={team.Abbreviation}";
39 | }
40 |
41 | public static string ReceiverTargets(string gameId)
42 | {
43 | string year = gameId.Substring(0, 4);
44 | return $"https://www.nfl.com/gamecenter/{gameId}/{year}/{gameId}/{gameId}";
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Http/WebRequestClient.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Components.Configurations;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Threading.Tasks;
7 |
8 | namespace R5.FFDB.Components.Http
9 | {
10 | public interface IWebRequestClient
11 | {
12 | void ReInitialize();
13 | Task GetStringAsync(string uri, bool throttle = true);
14 | }
15 |
16 | public class WebRequestClient : IWebRequestClient
17 | {
18 | private static HttpClient _client;
19 | private static CookieContainer _cookieContainer;
20 | private static HttpClientHandler _clientHandler;
21 | private static Random _random = new Random();
22 |
23 | private WebRequestConfig _config { get; }
24 |
25 | public WebRequestClient(WebRequestConfig config)
26 | {
27 | _config = config;
28 | }
29 |
30 | public void ReInitialize()
31 | {
32 | _client?.Dispose();
33 |
34 | _cookieContainer = new CookieContainer();
35 | _clientHandler = new HttpClientHandler
36 | {
37 | UseCookies = true,
38 | CookieContainer = _cookieContainer
39 | };
40 | _client = new HttpClient(_clientHandler);
41 |
42 | foreach(KeyValuePair header in _config.Headers)
43 | {
44 | _client.DefaultRequestHeaders.Add(header.Key, header.Value);
45 | }
46 | }
47 |
48 | public async Task GetStringAsync(string uri, bool throttle = true)
49 | {
50 | if (_client == null)
51 | {
52 | ReInitialize();
53 | }
54 |
55 | if (throttle)
56 | {
57 | await Task.Delay(GetRequestThrottle());
58 | }
59 |
60 | return await _client.GetStringAsync(uri);
61 | }
62 |
63 | private int GetRequestThrottle()
64 | {
65 | if (!_config.RandomizedThrottle.HasValue)
66 | {
67 | return _config.ThrottleMilliseconds;
68 | }
69 |
70 | return _random.Next(
71 | _config.RandomizedThrottle.Value.min,
72 | _config.RandomizedThrottle.Value.max + 1);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Http/WebRequestThrottle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace R5.FFDB.Components.Http
5 | {
6 | public class WebRequestThrottle
7 | {
8 | private Random _random { get; }
9 | private Func _getFunc { get; }
10 |
11 | public WebRequestThrottle(
12 | int throttle,
13 | (int min, int max)? randomizedThrottle = null)
14 | {
15 | if (!randomizedThrottle.HasValue)
16 | {
17 | _getFunc = () => throttle;
18 | }
19 | else
20 | {
21 | _random = new Random();
22 |
23 | _getFunc = () => _random.Next(
24 | randomizedThrottle.Value.min,
25 | randomizedThrottle.Value.max + 1);
26 | }
27 | }
28 |
29 | public Task DelayAsync()
30 | {
31 | return Task.Delay(_getFunc());
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Pipelines/Pipeline.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using R5.FFDB.Core;
3 | using R5.Internals.Abstractions.Pipeline;
4 | using R5.Internals.Extensions.DependencyInjection;
5 | using Serilog.Context;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Text;
9 |
10 | namespace R5.FFDB.Components.Pipelines
11 | {
12 | public abstract class Pipeline : AsyncPipeline
13 | {
14 | private IAppLogger _logger { get; }
15 | private IDisposable _contextProperty { get; set; }
16 | private Dictionary _stageContextProperties { get; } = new Dictionary();
17 | private IServiceProvider _serviceProvider { get; }
18 |
19 | protected Pipeline(
20 | IAppLogger logger,
21 | IServiceProvider serviceProvider,
22 | string name)
23 | : base(name)
24 | {
25 | _logger = logger;
26 | _serviceProvider = serviceProvider;
27 | }
28 |
29 | // Types should represent the implementations of AsyncPipelineStages
30 | protected abstract List Stages { get; }
31 |
32 | protected override List> GetStages()
33 | {
34 | var stages = new List>();
35 |
36 | foreach(var stageType in Stages)
37 | {
38 | var stage = _serviceProvider.Create(stageType) as AsyncPipelineStage;
39 | stages.Add(stage);
40 | }
41 |
42 | return stages;
43 | }
44 |
45 | protected override void OnPipelineProcessStart(TContext context, string name)
46 | {
47 | _contextProperty = LogContext.PushProperty("PipelineStage", name);
48 | _logger.LogInformation("Pipeline started.");
49 | }
50 |
51 | protected override void OnPipelineProcessEnd(TContext context, string name)
52 | {
53 | _logger.LogInformation("Pipeline completed.");
54 | _contextProperty?.Dispose();
55 | }
56 |
57 | protected override Guid OnStageProcessStart(TContext context, string name)
58 | {
59 | var id = Guid.NewGuid();
60 | _stageContextProperties[id] = LogContext.PushProperty("PipelineStage", name);
61 |
62 | _logger.LogInformation("Stage started.");
63 |
64 | return id;
65 | }
66 |
67 | protected override void OnStageProcessEnd(Guid stageId, TContext context, string name)
68 | {
69 | _logger.LogInformation("Stage completed.");
70 |
71 | if (_stageContextProperties.TryGetValue(stageId, out IDisposable stageContext))
72 | {
73 | stageContext?.Dispose();
74 | _stageContextProperties.Remove(stageId);
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/Pipelines/Stage.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core;
2 | using R5.Internals.Abstractions.Pipeline;
3 |
4 | namespace R5.FFDB.Components.Pipelines
5 | {
6 | public abstract class Stage : AsyncPipelineStage
7 | {
8 | private IAppLogger _logger { get; }
9 |
10 | protected Stage(
11 | IAppLogger logger,
12 | string name)
13 | : base(name)
14 | {
15 | _logger = logger;
16 | }
17 |
18 | protected void LogInformation(string message)
19 | {
20 | _logger.LogInformation(message);
21 | }
22 |
23 | protected void LogDebug(string message)
24 | {
25 | _logger.LogDebug(message);
26 | }
27 |
28 | protected void LogWarning(string message)
29 | {
30 | _logger.LogWarning(message);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/R5.FFDB.Components.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | latest
6 | R5.FFDB.Components
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/ValueProviders/AvailableWeeksValue.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 | using R5.Internals.Caching.ValueProviders;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace R5.FFDB.Components.ValueProviders
8 | {
9 | public class AvailableWeeksValue : AsyncValueProvider>
10 | {
11 | private LatestWeekValue _latestWeekValue { get; }
12 |
13 | public AvailableWeeksValue(LatestWeekValue latestWeekValue)
14 | : base("Available Weeks")
15 | {
16 | _latestWeekValue = latestWeekValue;
17 | }
18 |
19 | protected override async Task> ResolveValueAsync()
20 | {
21 | var result = new List();
22 |
23 | WeekInfo latest = await _latestWeekValue.GetAsync();
24 |
25 | // Earliest available is 2010-1
26 | for (int season = 2010; season < latest.Season; season++)
27 | {
28 | for (int week = 1; week <= 17; week++)
29 | {
30 | result.Add(new WeekInfo(season, week));
31 | }
32 | }
33 |
34 | for (int week = 1; week <= latest.Week; week++)
35 | {
36 | result.Add(new WeekInfo(latest.Season, week));
37 | }
38 |
39 | return result;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Components/ValueProviders/LatestWeekValue.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using R5.FFDB.Components.Http;
3 | using R5.FFDB.Core.Models;
4 | using R5.Internals.Caching.ValueProviders;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace R5.FFDB.Components.ValueProviders
10 | {
11 | public class LatestWeekValue : AsyncValueProvider
12 | {
13 | private IWebRequestClient _webRequestClient { get; }
14 |
15 | public LatestWeekValue(IWebRequestClient webRequestClient)
16 | : base("Latest Week")
17 | {
18 | _webRequestClient = webRequestClient;
19 | }
20 |
21 | protected override async Task ResolveValueAsync()
22 | {
23 | // doesn't matter which week we choose, it'll always return
24 | // the NFL's current state info
25 | JObject weekStats = await getWeekStatsAsync();
26 |
27 | (int currentSeason, int currentWeek) = getCurrentWeekInfo(weekStats);
28 |
29 | return new WeekInfo(currentSeason, currentWeek);
30 |
31 | // local functions
32 | async Task getWeekStatsAsync()
33 | {
34 | string uri = Endpoints.Api.WeekStats(new WeekInfo(2018, 1));
35 |
36 | string weekStatsJson = await _webRequestClient.GetStringAsync(uri, throttle: false);
37 |
38 | return JObject.Parse(weekStatsJson);
39 | }
40 |
41 | (int season, int week) getCurrentWeekInfo(JObject stats)
42 | {
43 | JObject games = stats["games"].ToObject();
44 |
45 | string gameId = games.Properties().Select(p => p.Name).First();
46 |
47 | int season = games[gameId]["season"].ToObject();
48 | int week = games[gameId]["state"]["week"].ToObject();
49 |
50 | bool isCompleted = games[gameId]["state"]["isWeekGamesCompleted"].ToObject();
51 | if (!isCompleted)
52 | {
53 | week = week - 1;
54 | }
55 |
56 | return (season, week);
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Database/IDatabaseProvider.cs:
--------------------------------------------------------------------------------
1 | namespace R5.FFDB.Core.Database
2 | {
3 | ///
4 | /// Represents the contract type the Engine requires to interface with a given database.
5 | ///
6 | public interface IDatabaseProvider
7 | {
8 | ///
9 | /// Returns the database context the Engine uses to make database calls and updates.
10 | ///
11 | IDatabaseContext GetContext();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Entities/Player.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.Core.Entities
6 | {
7 | ///
8 | /// Represents the player information required by the Engine for various functionality.
9 | ///
10 | public class Player
11 | {
12 | ///
13 | /// The player's database id.
14 | ///
15 | public Guid Id { get; set; }
16 |
17 | ///
18 | /// The player's official NFL id.
19 | ///
20 | public string NflId { get; set; }
21 |
22 | ///
23 | /// The player's ESB id.
24 | ///
25 | public string EsbId { get; set; }
26 |
27 | ///
28 | /// The player's GSIS id.
29 | ///
30 | public string GsisId { get; set; }
31 |
32 | ///
33 | /// The player's first name.
34 | ///
35 | public string FirstName { get; set; }
36 |
37 | ///
38 | /// The player's last name.
39 | ///
40 | public string LastName { get; set; }
41 |
42 | public override string ToString()
43 | {
44 | string name = $"{FirstName} {LastName}".Trim();
45 | return $"{NflId} ({name})";
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Entities/PlayerAdd.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace R5.FFDB.Core.Entities
4 | {
5 | ///
6 | /// Represents the data required to add a player.
7 | /// This is a combination of static and dynamic data points.
8 | ///
9 | public class PlayerAdd : PlayerUpdate
10 | {
11 | ///
12 | /// The player's first name.
13 | ///
14 | public string FirstName { get; set; }
15 |
16 | ///
17 | /// The player's last name.
18 | ///
19 | public string LastName { get; set; }
20 |
21 | ///
22 | /// The player's official NFL id.
23 | ///
24 | public string NflId { get; set; }
25 |
26 | ///
27 | /// The player's ESB id.
28 | ///
29 | public string EsbId { get; set; }
30 |
31 | ///
32 | /// The player's GSIS id.
33 | ///
34 | public string GsisId { get; set; }
35 |
36 | ///
37 | /// The player's official draft profile height.
38 | ///
39 | public int Height { get; set; }
40 |
41 | ///
42 | /// The player's official draft profile weight.
43 | ///
44 | public int Weight { get; set; }
45 |
46 | ///
47 | /// The player's date of birth.
48 | ///
49 | public DateTimeOffset DateOfBirth { get; set; }
50 |
51 | ///
52 | /// The college the player attended (if any)
53 | ///
54 | public string College { get; set; }
55 |
56 | public override string ToString()
57 | {
58 | string name = base.ToString();
59 | return $"{NflId} ({name})";
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Entities/PlayerUpdate.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 |
3 | namespace R5.FFDB.Core.Entities
4 | {
5 | ///
6 | /// Represents the update information for a player.
7 | /// Note that most of a player's profile data points are static and never change.
8 | /// This model represents some of the dynamic properties.
9 | ///
10 | public class PlayerUpdate
11 | {
12 | ///
13 | /// The player's current team number.
14 | ///
15 | public int? Number { get; set; }
16 |
17 | ///
18 | /// The player's current position.
19 | ///
20 | public Position? Position { get; set; }
21 |
22 | ///
23 | /// The player's current roster status.
24 | ///
25 | public RosterStatus? Status { get; set; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Entities/PlayerWeekStats.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace R5.FFDB.Core.Entities
8 | {
9 | ///
10 | /// Represents a single player's stats for a given week.
11 | ///
12 | public class PlayerWeekStats
13 | {
14 | ///
15 | /// The week the stats are tied to.
16 | ///
17 | public WeekInfo Week { get; set; }
18 |
19 | ///
20 | /// The NFL's official player id.
21 | ///
22 | public string NflId { get; set; }
23 |
24 | ///
25 | /// A map of stat types to numeric values.
26 | ///
27 | public Dictionary Stats { get; set; }
28 |
29 | ///
30 | /// The team the player was on for the week. This data point is
31 | /// resolved from more than one source, so it's nullable for the
32 | /// case we cannot resolve it.
33 | ///
34 | public int? TeamId { get; set; }
35 |
36 | public List> GetPassingStats()
37 | {
38 | return GetStatsByCategory(WeekStatCategory.Pass);
39 | }
40 |
41 | public List> GetRushingStats()
42 | {
43 | return GetStatsByCategory(WeekStatCategory.Rush);
44 | }
45 |
46 | public List> GetReceivingStats()
47 | {
48 | return GetStatsByCategory(WeekStatCategory.Receive);
49 | }
50 |
51 | public List> GetReturnStats()
52 | {
53 | return GetStatsByCategory(WeekStatCategory.Return);
54 | }
55 |
56 | public List> GetMiscStats()
57 | {
58 | return GetStatsByCategory(WeekStatCategory.Misc);
59 | }
60 |
61 | public List> GetKickingStats()
62 | {
63 | return GetStatsByCategory(WeekStatCategory.Kick);
64 | }
65 |
66 | public List> GetIdpStats()
67 | {
68 | return GetStatsByCategory(WeekStatCategory.IDP);
69 | }
70 |
71 | public List> GetDstStats()
72 | {
73 | return GetStatsByCategory(WeekStatCategory.DST);
74 | }
75 |
76 | private List> GetStatsByCategory(HashSet types)
77 | {
78 | return Stats.Where(kv => types.Contains(kv.Key)).ToList();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Entities/Roster.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace R5.FFDB.Core.Entities
7 | {
8 | ///
9 | /// Represents the current roster information for a given team.
10 | ///
11 | public class Roster
12 | {
13 | ///
14 | /// The team's id.
15 | ///
16 | public int TeamId { get; set; }
17 |
18 | ///
19 | /// The team's abbreviation.
20 | ///
21 | public string TeamAbbreviation { get; set; }
22 |
23 | ///
24 | /// The list of players currently rostered on the team.
25 | ///
26 | public List Players { get; set; }
27 |
28 | public override string ToString()
29 | {
30 | return $"{TeamAbbreviation} Roster";
31 | }
32 | }
33 |
34 | ///
35 | /// Represents a player that's currently rostered.
36 | ///
37 | public class RosterPlayer
38 | {
39 | ///
40 | /// The official NFL player id.
41 | ///
42 | public string NflId { get; set; }
43 |
44 | ///
45 | /// The player's current team number.
46 | ///
47 | public int? Number { get; set; }
48 |
49 | ///
50 | /// The player's current position type.
51 | ///
52 | public Position Position { get; set; }
53 |
54 | ///
55 | /// The player's current roster status type.
56 | ///
57 | public RosterStatus Status { get; set; }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Entities/Team.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.Core.Entities
6 | {
7 | ///
8 | /// Represents a single NFL team.
9 | ///
10 | public class Team
11 | {
12 | ///
13 | /// The id representing the team that's primarily used by the engine.
14 | ///
15 | public int Id { get; set; }
16 |
17 | ///
18 | /// The official NFL id of the team.
19 | ///
20 | public string NflId { get; set; }
21 |
22 | ///
23 | /// The official full name of the team (eg "Seattle Seahawks").
24 | ///
25 | public string Name { get; set; }
26 |
27 | ///
28 | /// The short name of the game (mainly used for URL mappings)
29 | ///
30 | public string ShortName { get; set; }
31 |
32 | ///
33 | /// The official abbreviation for the team (eg "SEA")
34 | ///
35 | public string Abbreviation { get; set; }
36 |
37 | ///
38 | /// Any known past abbreviations the team had used (eg "SD" for chargers, while current is "LAC")
39 | ///
40 | public HashSet PriorAbbreviations { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase);
41 |
42 | public override string ToString()
43 | {
44 | return Abbreviation;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/Entities/WeekMatchup.cs:
--------------------------------------------------------------------------------
1 | using R5.FFDB.Core.Models;
2 |
3 | namespace R5.FFDB.Core.Entities
4 | {
5 | ///
6 | /// Represents a single specific game from a given week between two teams.
7 | ///
8 | public class WeekMatchup
9 | {
10 | ///
11 | /// The week the game was played.
12 | ///
13 | public WeekInfo Week { get; set; }
14 |
15 | ///
16 | /// The home team's id.
17 | ///
18 | public int HomeTeamId { get; set; }
19 |
20 | ///
21 | /// The away team's id.
22 | ///
23 | public int AwayTeamId { get; set; }
24 |
25 | ///
26 | /// The NFL's official id for this game.
27 | ///
28 | public string NflGameId { get; set; }
29 |
30 | ///
31 | /// The NFL's official GSIS id for this game.
32 | ///
33 | public string GsisGameId { get; set; }
34 |
35 | public override string ToString()
36 | {
37 | return $"{HomeTeamId} vs {AwayTeamId} ({Week})";
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Engine/R5.FFDB.Core/IAppLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace R5.FFDB.Core
6 | {
7 | ///
8 | /// Represents the logger instance that's used for the built-in database providers.
9 | /// This is also provided for use in your own custom providers.
10 | ///
11 | public interface IAppLogger
12 | {
13 | void LogInformation(string messageTemplate);
14 | void LogInformation(string messageTemplate, T propertyValue);
15 | void LogDebug(string messageTemplate);
16 | void LogDebug(string messageTemplate, T propertyValue);
17 | void LogTrace(string messageTemplate);
18 | void LogTrace(string messageTemplate, T propertyValue);
19 | void LogError(Exception exception, string messageTemplate);
20 | void LogError