├── .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(Exception exception, string messageTemplate, T propertyValue); 21 | void LogWarning(string messageTemplate); 22 | void LogWarning(string messageTemplate, T propertyValue); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Isaiah Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Core/Models/Position.cs: -------------------------------------------------------------------------------- 1 | namespace R5.FFDB.Core.Models 2 | { 3 | /// 4 | /// Represents a player's position. 5 | /// 6 | public enum Position 7 | { 8 | QB, 9 | RB, 10 | FB, 11 | WR, 12 | TE, 13 | OL, 14 | C, 15 | G, 16 | LG, 17 | RG, 18 | T, 19 | LT, 20 | RT, 21 | K, 22 | KR, 23 | DL, 24 | DE, 25 | DT, 26 | NT, 27 | LB, 28 | ILB, 29 | OLB, 30 | MLB, 31 | DB, 32 | CB, 33 | FS, 34 | SS, 35 | S, 36 | P, 37 | PR, 38 | // added from scraping NFL roster pages 39 | OT, 40 | OG, 41 | LS, 42 | SAF 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Core/Models/RosterStatus.cs: -------------------------------------------------------------------------------- 1 | namespace R5.FFDB.Core.Models 2 | { 3 | /// 4 | /// Represents a player's current roster status. 5 | /// 6 | public enum RosterStatus 7 | { 8 | /// 9 | /// Active 10 | /// 11 | ACT, 12 | 13 | /// 14 | /// Injured Reserve 15 | /// 16 | RES, 17 | 18 | /// 19 | /// Non football-related Injured Reserve 20 | /// 21 | NON, 22 | 23 | /// 24 | /// Suspended 25 | /// 26 | SUS, 27 | 28 | /// 29 | /// Physically Unable to Perform 30 | /// 31 | PUP, 32 | 33 | /// 34 | /// Unsigned Draft Pick 35 | /// 36 | UDF, 37 | 38 | /// 39 | /// Exempt 40 | /// 41 | EXE 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Core/Models/WeekInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace R5.FFDB.Core.Models 4 | { 5 | /// 6 | /// Represents a single and specific week in a season. 7 | /// 8 | public struct WeekInfo : IComparable 9 | { 10 | public int Season { get; } 11 | public int Week { get; } 12 | 13 | public WeekInfo(int season, int week) 14 | { 15 | Season = season; 16 | Week = week; 17 | } 18 | 19 | public override bool Equals(object other) 20 | { 21 | if (!(other is WeekInfo)) 22 | { 23 | return false; 24 | } 25 | 26 | var otherWeek = (WeekInfo)other; 27 | 28 | return Season == otherWeek.Season && Week == otherWeek.Week; 29 | } 30 | 31 | public override int GetHashCode() 32 | { 33 | int hash = 17; 34 | hash = hash * 23 + Season.GetHashCode(); 35 | hash = hash * 23 + Week.GetHashCode(); 36 | return hash; 37 | } 38 | 39 | public override string ToString() 40 | { 41 | return $"{Season}-{Week}"; 42 | } 43 | 44 | public int CompareTo(WeekInfo other) 45 | { 46 | if (this.Season < other.Season) 47 | { 48 | return -1; 49 | } 50 | if (other.Season < this.Season) 51 | { 52 | return 1; 53 | } 54 | 55 | if (this.Week < other.Week) 56 | { 57 | return -1; 58 | } 59 | if (other.Week < this.Week) 60 | { 61 | return 1; 62 | } 63 | return 0; 64 | } 65 | 66 | public static bool operator ==(WeekInfo a, WeekInfo b) 67 | { 68 | return a.CompareTo(b) == 0; 69 | } 70 | 71 | public static bool operator !=(WeekInfo a, WeekInfo b) 72 | { 73 | return a.CompareTo(b) != 0; 74 | } 75 | 76 | public static bool operator <(WeekInfo a, WeekInfo b) 77 | { 78 | return a.CompareTo(b) == -1; 79 | } 80 | 81 | public static bool operator >(WeekInfo a, WeekInfo b) 82 | { 83 | return a.CompareTo(b) == 1; 84 | } 85 | 86 | public static bool operator <=(WeekInfo a, WeekInfo b) 87 | { 88 | return a.CompareTo(b) < 1; 89 | } 90 | 91 | public static bool operator >=(WeekInfo a, WeekInfo b) 92 | { 93 | return a.CompareTo(b) > -1; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Core/Models/WeekStatType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace R5.FFDB.Core.Models 6 | { 7 | /// 8 | /// Enumeration containing all the different stat types used by NFL's Fantasy API. 9 | /// The numeric values are what's received in the response. 10 | /// 11 | public enum WeekStatType 12 | { 13 | // Passing 14 | Pass_Attempts = 2, 15 | Pass_Completions = 3, 16 | Pass_Yards = 5, 17 | Pass_Touchdowns = 6, 18 | Pass_Interceptions = 7, 19 | Pass_Sacked = 8, 20 | // Rushing 21 | Rush_Attempts = 13, 22 | Rush_Yards = 14, 23 | Rush_Touchdowns = 15, 24 | // Receiving 25 | Receive_Catches = 20, 26 | Receive_Yards = 21, 27 | Receive_Touchdowns = 22, 28 | // Misc 29 | Return_Yards = 27, 30 | Return_Touchdowns = 28, 31 | Fumble_Recover_Touchdowns = 29, 32 | Fumbles_Lost = 30, 33 | Fumbles_Total = 31, 34 | TwoPointConversions = 32, 35 | // Kicking 36 | Kick_PAT_Makes = 33, 37 | Kick_PAT_Misses = 34, 38 | Kick_ZeroTwenty_Makes = 35, 39 | Kick_TwentyThirty_Makes = 36, 40 | Kick_ThirtyForty_Makes = 37, 41 | Kick_FortyFifty_Makes = 38, 42 | Kick_FiftyPlus_Makes = 39, 43 | Kick_ZeroTwenty_Misses = 40, 44 | Kick_TwentyThirty_Misses = 41, 45 | Kick_ThirtyForty_Misses = 42, 46 | Kick_FortyFifty_Misses = 43, 47 | Kick_FiftyPlus_Misses = 44, 48 | // DST 49 | DST_Sacks = 45, 50 | DST_Interceptions = 46, 51 | DST_FumblesRecovered = 47, 52 | DST_FumblesForced = 48, 53 | DST_Safeties = 49, 54 | DST_Touchdowns = 50, 55 | DST_BlockedKicks = 51, 56 | DST_ReturnYards = 52, 57 | DST_ReturnTouchdowns = 53, 58 | DST_PointsAllowed = 54, 59 | DST_YardsAllowed = 62, 60 | // IDP 61 | IDP_Tackles = 70, 62 | IDP_AssistedTackles = 71, 63 | IDP_Sacks = 72, 64 | IDP_Interceptions = 73, 65 | IDP_ForcedFumbles = 74, 66 | IDP_FumblesRecovered = 75, 67 | IDP_InterceptionTouchdowns = 76, 68 | IDP_FumbleTouchdowns = 77, 69 | IDP_BlockedKickTouchdowns = 78, 70 | IDP_BlockedKicks = 79, 71 | IDP_Safeties = 80, 72 | IDP_PassesDefended = 81, 73 | IDP_InterceptionReturnYards = 82, 74 | IDP_FumbleReturnYards = 83, 75 | IDP_TacklesForLoss = 84, 76 | IDP_QuarterBackHits = 85, 77 | IDP_SackYards = 86 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Core/R5.FFDB.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | latest 6 | R5.FFDB.Core 7 | R5.FFDB.Core 8 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 9 | Debug;Release;Test 10 | Isaiah Lee 11 | 12 | Contains the core models used by the FFDB Engine and the associated database providers. 13 | 1.0.0-alpha.1 14 | https://github.com/rushfive/FFDB 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Core/R5.FFDB.Core.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | R5.FFDB.Core 5 | 1.0.0-alpha.1 6 | R5.FFDB.Core 7 | Isaiah Lee 8 | Isaiah Lee 9 | https://github.com/rushfive/FFDB 10 | false 11 | Contains the core models used by the FFDB Engine and the associated database providers. 12 | Alpha release - Feature complete but may include database schema changes for v1. 13 | Copyright 2019 14 | NFL fantasy football database stats 15 | 16 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Engine/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Isaiah Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Engine/Processors/PlayerProcessor.cs: -------------------------------------------------------------------------------- 1 | using R5.FFDB.Components.Pipelines.Players; 2 | using R5.Internals.Extensions.DependencyInjection; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace R5.FFDB.Engine.Processors 7 | { 8 | /// 9 | /// Contains the Engine tasks specific to players. 10 | /// 11 | public class PlayerProcessor 12 | { 13 | private IServiceProvider _serviceProvider { get; } 14 | 15 | public PlayerProcessor(IServiceProvider serviceProvider) 16 | { 17 | _serviceProvider = serviceProvider; 18 | } 19 | 20 | /// 21 | /// Updates the dynamic information for players currently rostered on a team. 22 | /// 23 | /// 24 | public Task UpdateCurrentlyRosteredAsync() 25 | { 26 | var context = new UpdateCurrentlyRosteredPipeline.Context(); 27 | 28 | var pipeline = _serviceProvider.Create(); 29 | 30 | return pipeline.ProcessAsync(context); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Engine/Processors/StatsProcessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using R5.FFDB.Components.Pipelines.Stats; 3 | using R5.FFDB.Core.Models; 4 | using R5.Internals.Extensions.DependencyInjection; 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | namespace R5.FFDB.Engine.Processors 9 | { 10 | /// 11 | /// Contains the Engine tasks specific to stats. 12 | /// 13 | public class StatsProcessor 14 | { 15 | private IServiceProvider _serviceProvider { get; } 16 | 17 | public StatsProcessor(IServiceProvider serviceProvider) 18 | { 19 | _serviceProvider = serviceProvider; 20 | } 21 | 22 | /// 23 | /// Add stats for every possible week that's missing in the database. 24 | /// 25 | public Task AddMissingAsync() 26 | { 27 | var context = new UpdateMissingPipeline.Context(); 28 | 29 | var pipeline = _serviceProvider.Create(); 30 | 31 | return pipeline.ProcessAsync(context); 32 | } 33 | 34 | /// 35 | /// Add stats for the specified week. 36 | /// 37 | public Task AddForWeekAsync(WeekInfo week) 38 | { 39 | var context = new AddForWeekPipeline.Context 40 | { 41 | Week = week 42 | }; 43 | 44 | var pipeline = _serviceProvider.Create(); 45 | 46 | return pipeline.ProcessAsync(context); 47 | } 48 | 49 | /// 50 | /// Add stats for the specified week. 51 | /// 52 | public Task AddForWeekAsync(int season, int week) => AddForWeekAsync(new WeekInfo(season, week)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Engine/Processors/TeamProcessor.cs: -------------------------------------------------------------------------------- 1 | using R5.FFDB.Components.Pipelines.Teams; 2 | using R5.Internals.Extensions.DependencyInjection; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace R5.FFDB.Engine.Processors 7 | { 8 | /// 9 | /// Contains the Engine tasks specific to teams. 10 | /// 11 | public class TeamProcessor 12 | { 13 | private IServiceProvider _serviceProvider { get; } 14 | 15 | public TeamProcessor(IServiceProvider serviceProvider) 16 | { 17 | _serviceProvider = serviceProvider; 18 | } 19 | 20 | /// 21 | /// Update the player-to-team mappings based off of the current roster information. 22 | /// 23 | public Task UpdateRosterMappingsAsync() 24 | { 25 | var context = new UpdateRosterMappingsPipeline.Context(); 26 | 27 | var pipeline = _serviceProvider.Create(); 28 | 29 | return pipeline.ProcessAsync(context); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Engine/R5.FFDB.Engine.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | latest 6 | R5.FFDB.Engine 7 | R5.FFDB.Engine 8 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 9 | Debug;Release;Test 10 | Isaiah Lee 11 | 12 | The FFDB Engine that interfaces with the data sources and db providers. 13 | 1.0.0-alpha.1 14 | https://github.com/rushfive/FFDB 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Engine/R5.FFDB.Engine/R5.FFDB.Engine.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | R5.FFDB.Engine 5 | 1.0.0-alpha.1 6 | R5.FFDB.Engine 7 | Isaiah Lee 8 | Isaiah Lee 9 | https://github.com/rushfive/FFDB 10 | false 11 | The FFDB Engine that interfaces with the data sources and db providers. 12 | Alpha release - Feature complete but may include database schema changes for v1. 13 | Copyright 2019 14 | NFL fantasy football database stats 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Isaiah Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Abstractions/Pipeline/AsyncPipelineStage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace R5.Internals.Abstractions.Pipeline 7 | { 8 | public class AsyncPipelineStage 9 | { 10 | public string Name { get; } 11 | public AsyncPipelineStage Next { get; private set; } 12 | 13 | public AsyncPipelineStage(string name) 14 | { 15 | Name = name; 16 | } 17 | 18 | public virtual Task ShouldSkipAsync(TContext context) 19 | { 20 | return Task.FromResult(false); 21 | } 22 | 23 | public virtual Func> OnProcessAsync { get; set; } 24 | 25 | public virtual async Task ProcessAsync(TContext context) 26 | { 27 | return OnProcessAsync != null 28 | ? await OnProcessAsync(context) 29 | : ProcessResult.End; 30 | } 31 | 32 | public AsyncPipelineStage SetNext(AsyncPipelineStage nextStage) 33 | { 34 | Next = nextStage; 35 | return Next; 36 | } 37 | 38 | public AsyncPipelineStage SetNext(string name, 39 | Func> onProcessCallback) 40 | { 41 | Next = new AsyncPipelineStage(name) 42 | { 43 | OnProcessAsync = onProcessCallback 44 | }; 45 | 46 | return Next; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Abstractions/Pipeline/ProcessStageResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace R5.Internals.Abstractions.Pipeline 6 | { 7 | public static class ProcessResult 8 | { 9 | public static Continue Continue => new Continue(); 10 | 11 | public static End End => new End(); 12 | } 13 | 14 | public abstract class ProcessStageResult { } 15 | public class Continue : ProcessStageResult { } 16 | public class End : ProcessStageResult { } 17 | } 18 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Abstractions/R5.Internals.Abstractions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | latest 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Abstractions/SystemConsole/ConsoleManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using static System.Console; 5 | using CM = R5.Internals.Abstractions.SystemConsole.ConsoleManager; 6 | 7 | namespace R5.Internals.Abstractions.SystemConsole 8 | { 9 | public static class ConsoleManager 10 | { 11 | public static void WriteLineColored(string value, ConsoleColor color) 12 | { 13 | ForegroundColor = color; 14 | WriteLine(value); 15 | } 16 | 17 | public static void WriteLineColoredReset(string value, ConsoleColor color) 18 | { 19 | CM.WriteLineColored(value, color); 20 | ResetColor(); 21 | } 22 | 23 | public static void WriteColored(string value, ConsoleColor color) 24 | { 25 | ForegroundColor = color; 26 | Write(value); 27 | } 28 | 29 | public static void WriteColoredReset(string value, ConsoleColor color) 30 | { 31 | CM.WriteColored(value, color); 32 | ResetColor(); 33 | } 34 | 35 | public static void WriteError(string message) 36 | { 37 | Console.ForegroundColor = ConsoleColor.Red; 38 | Console.WriteLine(message); 39 | Console.ResetColor(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Abstractions/Utilities/RangedListBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace R5.Internals.Abstractions.Utilities 7 | { 8 | public static class RangedListBuilder 9 | { 10 | public static List Build(List numbers) 11 | { 12 | if (numbers == null || !numbers.Any()) 13 | { 14 | throw new ArgumentException("At least one number must be provided."); 15 | } 16 | 17 | var result = new List(); 18 | 19 | numbers.Sort(); 20 | var currentRange = new List(); 21 | 22 | for (int i = 0; i < numbers.Count; i++) 23 | { 24 | if (!currentRange.Any()) 25 | { 26 | currentRange.Add(numbers[i]); 27 | continue; 28 | } 29 | 30 | if (currentRange.Last() == numbers[i] - 1) 31 | { 32 | currentRange.Add(numbers[i]); 33 | continue; 34 | } 35 | 36 | // end of a "range", add to result 37 | result.Add(GetRangeLabel(currentRange)); 38 | currentRange.Clear(); 39 | currentRange.Add(numbers[i]); 40 | } 41 | 42 | if (currentRange.Any()) 43 | { 44 | result.Add(GetRangeLabel(currentRange)); 45 | } 46 | 47 | return result; 48 | } 49 | 50 | private static string GetRangeLabel(List range) 51 | { 52 | if (range.Count == 1) 53 | { 54 | return range.First().ToString(); 55 | } 56 | else 57 | { 58 | return $"{range.First()}-{range.Last()}"; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Caching/R5.Internals.Caching.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Caching/ValueProviders/AsyncValueProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace R5.Internals.Caching.ValueProviders 8 | { 9 | // lazy loaded values resolved async 10 | public abstract class AsyncValueProvider 11 | { 12 | private T _value { get; set; } 13 | private bool _isSet { get; set; } 14 | private string _valueLabel { get; set; } 15 | 16 | private SemaphoreSlim _lock = new SemaphoreSlim(1, 1); 17 | 18 | protected AsyncValueProvider(string valueLabel) 19 | { 20 | _valueLabel = valueLabel; 21 | } 22 | 23 | public async Task GetAsync() 24 | { 25 | if (_isSet) 26 | { 27 | return _value; 28 | } 29 | 30 | await _lock.WaitAsync(); 31 | try 32 | { 33 | if (_isSet) 34 | { 35 | return _value; 36 | } 37 | 38 | _value = await ResolveValueAsync(); 39 | _isSet = true; 40 | 41 | return _value; 42 | } 43 | catch (Exception ex) 44 | { 45 | throw new InvalidOperationException($"Failed to resolve async value for '{_valueLabel}'.", ex); 46 | } 47 | finally 48 | { 49 | _lock.Release(); 50 | } 51 | } 52 | 53 | protected abstract Task ResolveValueAsync(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Extensions/Collections/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace R5.Internals.Extensions.Collections 7 | { 8 | public static class ListExtensions 9 | { 10 | public static bool IsNullOrEmpty(this List list) 11 | { 12 | return list == null || !list.Any(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Extensions/DependencyInjection/ServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace R5.Internals.Extensions.DependencyInjection 5 | { 6 | public static class ServiceProviderExtensions 7 | { 8 | public static T Create(this IServiceProvider provider, params object[] parameters) 9 | { 10 | return ActivatorUtilities.CreateInstance(provider, parameters); 11 | } 12 | 13 | public static object Create(this IServiceProvider provider, Type instanceType, params object[] parameters) 14 | { 15 | return ActivatorUtilities.CreateInstance(provider, instanceType, parameters); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Extensions/R5.Internals.Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Extensions/Reflection/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace R5.Internals.Extensions.Reflection 8 | { 9 | public static class ExpressionExtensions 10 | { 11 | public static PropertyInfo GetProperty(this Expression> expression) 12 | { 13 | MemberExpression memberExpression = null; 14 | 15 | if (expression.Body.NodeType == ExpressionType.Convert) 16 | { 17 | memberExpression = ((UnaryExpression)expression.Body).Operand as MemberExpression; 18 | } 19 | else if (expression.Body.NodeType == ExpressionType.MemberAccess) 20 | { 21 | memberExpression = expression.Body as MemberExpression; 22 | } 23 | 24 | if (memberExpression == null) 25 | { 26 | throw new ArgumentException("Not a member access", "expression"); 27 | } 28 | 29 | return memberExpression.Member as PropertyInfo; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Extensions/Reflection/MemberInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace R5.Internals.Extensions.Reflection 7 | { 8 | public static class MemberInfoExtensions 9 | { 10 | public static Type GetUnderlyingType(this MemberInfo member) 11 | { 12 | switch (member.MemberType) 13 | { 14 | case MemberTypes.Event: 15 | return ((EventInfo)member).EventHandlerType; 16 | case MemberTypes.Field: 17 | return ((FieldInfo)member).FieldType; 18 | case MemberTypes.Method: 19 | return ((MethodInfo)member).ReturnType; 20 | case MemberTypes.Property: 21 | return ((PropertyInfo)member).PropertyType; 22 | default: 23 | throw new ArgumentException($"'{member.MemberType}' is invalid or unhandled.", nameof(member.MemberType)); 24 | } 25 | } 26 | 27 | public static bool TryGetPropertyType(this MemberInfo member, out Type type) 28 | { 29 | type = null; 30 | 31 | if (member.MemberType == MemberTypes.Property) 32 | { 33 | type = ((PropertyInfo)member).PropertyType; 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Extensions/Reflection/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace R5.Internals.Extensions.Reflection 8 | { 9 | public static class TypeExtensions 10 | { 11 | public static bool TryGetNullableUnderlyingType(this Type type, out Type underlying) 12 | { 13 | underlying = Nullable.GetUnderlyingType(type); 14 | return underlying != null; 15 | } 16 | 17 | public static bool IsNullable(this Type type) 18 | { 19 | return Nullable.GetUnderlyingType(type) != null; 20 | } 21 | 22 | public static bool IsNullableEnum(this Type type) 23 | { 24 | Type underlyingType = Nullable.GetUnderlyingType(type); 25 | return (underlyingType != null) && underlyingType.IsEnum; 26 | } 27 | 28 | public static T GetCustomAttributeOrNull(this Type type) 29 | where T : Attribute 30 | { 31 | return type.GetCustomAttributes(typeof(T), true).FirstOrDefault() as T; 32 | } 33 | 34 | public static List GetPropertiesContainingAttribute(this Type type) 35 | { 36 | return type 37 | .GetProperties() 38 | .Where(p => p.GetCustomAttributes() 39 | .Any(a => a.GetType() == typeof(T))) 40 | .ToList(); 41 | } 42 | 43 | public static List GetPropertiesContainingBaseAttribute(this Type type) 44 | { 45 | return type.GetProperties() 46 | .Where(p => p.GetCustomAttributes() 47 | .Any(a => a.GetType().BaseType == typeof(T))) 48 | .ToList(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /R5.Internals/R5.Internals.Extensions/Serialization/NewtonsoftExtensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace R5.Internals.Extensions.Serialization 8 | { 9 | public static class NewtonsoftExtensions 10 | { 11 | public static bool TryGetToken(this JToken root, string key, out JToken token) 12 | { 13 | token = root[key]; 14 | return token != null; 15 | } 16 | 17 | public static List ChildPropertyNames(this JToken root) 18 | { 19 | return root.Children().Select(t => ((JProperty)t).Name).ToList(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: InternalsVisibleTo("R5.Internals.PostgresMapper.Tests")] 5 | [assembly: InternalsVisibleTo("DevTester")] 6 | [assembly: CLSCompliant(true)] -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/Attributes/ColumnAttributes.cs: -------------------------------------------------------------------------------- 1 | using R5.Internals.PostgresMapper.Mappers; 2 | using R5.Internals.PostgresMapper.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace R5.Internals.PostgresMapper.Attributes 8 | { 9 | [AttributeUsage(AttributeTargets.Property)] 10 | public abstract class EntityColumnAttribute : Attribute 11 | { 12 | public abstract void UpdateTableColumn(TableColumn column); 13 | } 14 | 15 | public class ColumnAttribute : EntityColumnAttribute 16 | { 17 | public string Name { get; } 18 | public PostgresDataType? DataType { get; } 19 | 20 | public ColumnAttribute(string name) 21 | { 22 | Name = name; 23 | } 24 | 25 | public ColumnAttribute(string name, PostgresDataType dataType) 26 | { 27 | Name = name; 28 | DataType = dataType; 29 | } 30 | 31 | public override void UpdateTableColumn(TableColumn column) 32 | { 33 | // name and datatype are set separately 34 | } 35 | } 36 | 37 | public class PrimaryKeyAttribute : EntityColumnAttribute 38 | { 39 | public override void UpdateTableColumn(TableColumn column) 40 | { 41 | column.SetAsPrimaryKey(); 42 | } 43 | } 44 | 45 | public class NotNullAttribute : EntityColumnAttribute 46 | { 47 | public override void UpdateTableColumn(TableColumn column) 48 | { 49 | column.SetAsNotNull(); 50 | } 51 | } 52 | // make props private 53 | public class ForeignKeyAttribute : EntityColumnAttribute 54 | { 55 | private Type _foreignTableType { get; } 56 | private string _foreignColumnName { get; } 57 | 58 | public ForeignKeyAttribute( 59 | Type foreignTableType, 60 | string foreignColumnName) 61 | { 62 | _foreignTableType = foreignTableType; 63 | _foreignColumnName = foreignColumnName; 64 | } 65 | 66 | public override void UpdateTableColumn(TableColumn column) 67 | { 68 | column.SetForeignTableType(_foreignTableType); 69 | column.SetForeignKeyColumn(_foreignColumnName); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/Attributes/TableAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace R5.Internals.PostgresMapper.Attributes 7 | { 8 | [AttributeUsage(AttributeTargets.Class)] 9 | public abstract class EntityTableAttribute : Attribute { } 10 | 11 | public class TableAttribute : EntityTableAttribute 12 | { 13 | public string Name { get; } 14 | 15 | public TableAttribute(string name) 16 | { 17 | Name = name; 18 | } 19 | } 20 | 21 | public class CompositePrimaryKeysAttribute : EntityTableAttribute 22 | { 23 | public List ColumnNames { get; } 24 | 25 | public CompositePrimaryKeysAttribute(params string[] columnNames) 26 | { 27 | if (columnNames == null || columnNames.Length < 2) 28 | { 29 | throw new ArgumentNullException(nameof(columnNames), 30 | "At least two column names must be provided for use as a table's composite primary key."); 31 | } 32 | 33 | ColumnNames = columnNames.ToList(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/DebugUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using CM = R5.Internals.Abstractions.SystemConsole.ConsoleManager; 5 | using static System.Console; 6 | 7 | namespace R5.Internals.PostgresMapper 8 | { 9 | internal static class DebugUtil 10 | { 11 | private static string _sqlCommandBeginBorder { get; } 12 | = @"################# SQL Command #################"; 13 | 14 | private static string _sqlCommandEndBorder { get; } 15 | = @"###############################################"; 16 | 17 | internal static void OutputSqlCommand(string sqlCommand) 18 | { 19 | CM.WriteLineColored( 20 | Environment.NewLine + _sqlCommandBeginBorder + Environment.NewLine, 21 | ConsoleColor.Yellow); 22 | 23 | WriteLine(sqlCommand); 24 | 25 | CM.WriteLineColoredReset( 26 | Environment.NewLine + _sqlCommandEndBorder + Environment.NewLine, 27 | ConsoleColor.Yellow); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/Mappers/DbValueToObjectMapper.cs: -------------------------------------------------------------------------------- 1 | using R5.Internals.Extensions.Reflection; 2 | using R5.Internals.PostgresMapper.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace R5.Internals.PostgresMapper.Mappers 8 | { 9 | internal static class DbValueToObjectMapper 10 | { 11 | internal static object Map(object value, 12 | Type propertyType, PostgresDataType dataType) 13 | { 14 | if (propertyType.IsNullable()) 15 | { 16 | return ResolveValueForNullable(value, propertyType, dataType); 17 | } 18 | 19 | return ResolveValue(value, propertyType, dataType); 20 | } 21 | 22 | private static object ResolveValueForNullable(object value, 23 | Type propertyType, PostgresDataType dataType) 24 | { 25 | if (value == null || value.GetType() == typeof(DBNull)) 26 | { 27 | return null; 28 | } 29 | 30 | if (propertyType.IsNullableEnum()) 31 | { 32 | Type nullableType = Nullable.GetUnderlyingType(propertyType); 33 | return Enum.Parse(nullableType, (string)value); 34 | } 35 | 36 | return ResolveValue(value, propertyType, dataType); 37 | } 38 | 39 | private static object ResolveValue(object value, 40 | Type propertyType, PostgresDataType dataType) 41 | { 42 | object converted = value; 43 | 44 | switch (dataType) 45 | { 46 | case PostgresDataType.TEXT: 47 | if (propertyType.IsEnum) 48 | { 49 | converted = Enum.Parse(propertyType, (string)value); 50 | } 51 | break; 52 | case PostgresDataType.DATE: 53 | case PostgresDataType.TIMESTAMPTZ: 54 | DateTime dt = (DateTime)value; 55 | dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc); 56 | 57 | DateTimeOffset dtOffset = dt; 58 | converted = dtOffset; 59 | break; 60 | case PostgresDataType.UUID: 61 | case PostgresDataType.INT: 62 | case PostgresDataType.FLOAT8: 63 | case PostgresDataType.SQL_IDENTIFIER: 64 | break; 65 | default: 66 | throw new ArgumentOutOfRangeException(nameof(dataType), $"'{dataType}' is an invalid '{nameof(PostgresDataType)}'."); 67 | } 68 | 69 | return converted; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/Mappers/ToPostgresDataTypeMapper.cs: -------------------------------------------------------------------------------- 1 | using R5.Internals.PostgresMapper.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace R5.Internals.PostgresMapper.Mappers 7 | { 8 | public static class ToPostgresDataTypeMapper 9 | { 10 | private static Dictionary _toPostgresMap = new Dictionary 11 | { 12 | { typeof(Guid), PostgresDataType.UUID }, 13 | { typeof(string), PostgresDataType.TEXT }, 14 | { typeof(int), PostgresDataType.INT }, 15 | { typeof(double), PostgresDataType.FLOAT8 }, 16 | { typeof(DateTimeOffset), PostgresDataType.TIMESTAMPTZ }, 17 | { typeof(bool), PostgresDataType.BOOLEAN } 18 | }; 19 | 20 | public static PostgresDataType Map() 21 | { 22 | return Map(typeof(TPropertyType)); 23 | } 24 | 25 | public static PostgresDataType Map(Type propertyType) 26 | { 27 | Type underlyingType = Nullable.GetUnderlyingType(propertyType); 28 | 29 | if (underlyingType != null) 30 | { 31 | propertyType = underlyingType; 32 | } 33 | 34 | if (!_toPostgresMap.TryGetValue(propertyType, out PostgresDataType pgType)) 35 | { 36 | throw new InvalidOperationException($"Failed to map property type '{propertyType.Name}' to a postgres data type."); 37 | } 38 | 39 | return pgType; 40 | } 41 | 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/Models/PostgresDataType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace R5.Internals.PostgresMapper.Models 6 | { 7 | public enum PostgresDataType 8 | { 9 | UUID, 10 | TEXT, 11 | INT, 12 | TIMESTAMPTZ, 13 | FLOAT8, 14 | DATE, 15 | BOOLEAN, 16 | 17 | // System 18 | SQL_IDENTIFIER 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/Models/SqlEntity.cs: -------------------------------------------------------------------------------- 1 | using R5.Internals.PostgresMapper.Attributes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace R5.Internals.PostgresMapper.Models 8 | { 9 | // do we even really need this base class?? 10 | //public abstract class SqlEntity 11 | //{ 12 | // //public abstract string TableName { get; } 13 | // //public string TableName => MetadataResolver.TableName(this.GetType()); 14 | // //public List Columns() => MetadataResolver.TableColumns(this.GetType()); 15 | //} 16 | 17 | 18 | [Table("Test")] 19 | public class TestEntity// : SqlEntity 20 | { 21 | [Column("number1")] 22 | public int Number1 { get; set; } 23 | [Column("number2")] 24 | public int Number2 { get; set; } 25 | 26 | [Column("StringColName")] 27 | public string String { get; set; } 28 | 29 | [Column("IntColName")] 30 | public int Int { get; set; } 31 | 32 | [Column("NullableDoubleColName")] 33 | public double? NullableDouble { get; set; } 34 | 35 | [Column("NullableDoubleColName2")] 36 | public double? NullableDouble2 { get; set; } 37 | 38 | [Column("BoolColName")] 39 | public bool Bool { get; set; } 40 | 41 | [Column("DateTime")] 42 | public DateTime DateTime { get; set; } 43 | } 44 | 45 | [Table("Test2")] 46 | public class TestEntity2// : SqlEntity 47 | { 48 | public string String { get; set; } 49 | 50 | [Column("Int")] 51 | public int Int { get; set; } 52 | 53 | [Column("NullableDouble")] 54 | public double? NullableDouble { get; set; } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/NpgsqlExtensions.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace R5.Internals.PostgresMapper 9 | { 10 | internal static class NpgsqlExtensions 11 | { 12 | public static async Task ExecuteReaderAsync(this NpgsqlConnection connection, 13 | string sqlCommand, Func onReadMapper) 14 | { 15 | if (onReadMapper == null) 16 | { 17 | throw new ArgumentNullException(nameof(onReadMapper), "On-read mapper callback must be provided to execute a command with returning value."); 18 | } 19 | 20 | using (connection) 21 | { 22 | await connection.OpenAsync(); 23 | 24 | using (var command = new NpgsqlCommand()) 25 | { 26 | command.Connection = connection; 27 | command.CommandText = sqlCommand; 28 | 29 | using (NpgsqlDataReader reader = command.ExecuteReader()) 30 | { 31 | return onReadMapper.Invoke(reader); 32 | } 33 | } 34 | } 35 | } 36 | 37 | public static async Task ExecuteNonQueryAsync(this NpgsqlConnection connection, 38 | string sqlCommand, List<(string key, string value)> parameters = null) 39 | { 40 | using (connection) 41 | { 42 | await connection.OpenAsync(); 43 | 44 | using (var command = new NpgsqlCommand()) 45 | { 46 | command.Connection = connection; 47 | command.CommandText = sqlCommand; 48 | 49 | if (parameters?.Any() ?? false) 50 | { 51 | parameters.ForEach(p => command.Parameters.AddWithValue(p.key, p.value)); 52 | } 53 | 54 | await command.ExecuteNonQueryAsync(); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/QueryCommand/CreateTableCommand.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using R5.Internals.Extensions.Collections; 3 | using R5.Internals.PostgresMapper.SqlBuilders; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace R5.Internals.PostgresMapper.QueryCommand 12 | { 13 | public class CreateTableCommand 14 | { 15 | private Func _getConnection { get; } 16 | private ConcatSqlBuilder _sqlBuilder { get; } = new ConcatSqlBuilder(); 17 | 18 | public CreateTableCommand( 19 | Func getConnection) 20 | { 21 | _getConnection = getConnection ?? throw new ArgumentNullException(nameof(getConnection)); 22 | BuildCreateCommand(); 23 | } 24 | 25 | private void BuildCreateCommand() 26 | { 27 | List columnDefinitions = GetColumnDefinitions(); 28 | 29 | _sqlBuilder 30 | .Append($"CREATE TABLE {MetadataResolver.TableName()} ") 31 | .Append($" ({string.Join(", ", columnDefinitions)}) "); 32 | } 33 | 34 | private List GetColumnDefinitions() 35 | { 36 | List definitions = MetadataResolver.TableColumns() 37 | .Select(c => c.DefinitionForCreateTable()) 38 | .ToList(); 39 | 40 | List compositeKeys = MetadataResolver.CompositePrimaryKeys(); 41 | if (!compositeKeys.IsNullOrEmpty()) 42 | { 43 | definitions.Add( 44 | $"PRIMARY KEY({string.Join(", ", compositeKeys)})"); 45 | } 46 | 47 | return definitions; 48 | } 49 | 50 | public string GetSqlCommand() 51 | { 52 | return _sqlBuilder.GetResult(); 53 | } 54 | 55 | public Task ExecuteAsync() 56 | { 57 | var sqlCommand = GetSqlCommand(); 58 | #if DEBUG 59 | DebugUtil.OutputSqlCommand(sqlCommand); 60 | #endif 61 | NpgsqlConnection connection = _getConnection(); 62 | return connection.ExecuteNonQueryAsync(sqlCommand); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/QueryCommand/DeleteCommand.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using R5.Internals.PostgresMapper.SqlBuilders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace R5.Internals.PostgresMapper.QueryCommand 9 | { 10 | public class DeleteCommand 11 | where TEntity : class 12 | { 13 | private Func _getConnection { get; } 14 | private ConcatSqlBuilder _sqlBuilder { get; } = new ConcatSqlBuilder(); 15 | 16 | public DeleteCommand( 17 | Func getConnection, 18 | TEntity entity) 19 | { 20 | _getConnection = getConnection ?? throw new ArgumentNullException(nameof(getConnection)); 21 | if (entity == null) 22 | { 23 | throw new ArgumentNullException(nameof(entity), "Entity to be deleted must be provided."); 24 | } 25 | 26 | AppendDeleteFrom(); 27 | AppendWhereCondition(entity); 28 | } 29 | 30 | private void AppendDeleteFrom() 31 | { 32 | var tableName = MetadataResolver.TableName(); 33 | _sqlBuilder.Append($"DELETE FROM {tableName}"); 34 | } 35 | 36 | private void AppendWhereCondition(TEntity entity) 37 | { 38 | Func getKeyMatch = MetadataResolver.GetPrimaryKeyMatchConditionFunc(); 39 | 40 | string condition = getKeyMatch(entity); 41 | 42 | _sqlBuilder.Append($"WHERE {condition}"); 43 | } 44 | 45 | public string GetSqlCommand() 46 | { 47 | return _sqlBuilder.GetResult(); 48 | } 49 | 50 | public Task ExecuteAsync() 51 | { 52 | var sqlCommand = GetSqlCommand(); 53 | #if DEBUG 54 | DebugUtil.OutputSqlCommand(sqlCommand); 55 | #endif 56 | NpgsqlConnection connection = _getConnection(); 57 | return connection.ExecuteNonQueryAsync(sqlCommand); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/QueryCommand/DeleteWhereCommand.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using R5.Internals.PostgresMapper.SqlBuilders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace R5.Internals.PostgresMapper.QueryCommand 10 | { 11 | public class DeleteWhereCommand where TEntity : class 12 | { 13 | private Func _getConnection { get; } 14 | private ConcatSqlBuilder _sqlBuilder { get; } = new ConcatSqlBuilder(); 15 | 16 | public DeleteWhereCommand( 17 | Func getConnection, 18 | Expression> conditionExpression) 19 | { 20 | _getConnection = getConnection ?? throw new ArgumentNullException(nameof(getConnection)); 21 | if (conditionExpression == null) 22 | { 23 | throw new ArgumentNullException(nameof(conditionExpression), "Condition expression must be provided.."); 24 | } 25 | 26 | AppendDeleteFrom(); 27 | AppendWhereCondition(conditionExpression); 28 | } 29 | 30 | private void AppendDeleteFrom() 31 | { 32 | var tableName = MetadataResolver.TableName(); 33 | _sqlBuilder.Append($"DELETE FROM {tableName}"); 34 | } 35 | 36 | private void AppendWhereCondition(Expression> conditionExpression) 37 | { 38 | string whereCondition = WhereConditionBuilder.FromExpression(conditionExpression); 39 | string where = $"WHERE {whereCondition}"; 40 | 41 | _sqlBuilder.Append(where); 42 | } 43 | 44 | public string GetSqlCommand() 45 | { 46 | return _sqlBuilder.GetResult(); 47 | } 48 | 49 | public Task ExecuteAsync() 50 | { 51 | var sqlCommand = GetSqlCommand(); 52 | #if DEBUG 53 | DebugUtil.OutputSqlCommand(sqlCommand); 54 | #endif 55 | NpgsqlConnection connection = _getConnection(); 56 | return connection.ExecuteNonQueryAsync(sqlCommand); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/QueryCommand/ExistsQuery.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using R5.Internals.PostgresMapper.SqlBuilders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace R5.Internals.PostgresMapper.QueryCommand 10 | { 11 | // todo internal 12 | public class ExistsQuery 13 | { 14 | private Func _getConnection { get; } 15 | private ConcatSqlBuilder _sqlBuilder { get; } = new ConcatSqlBuilder(); 16 | 17 | public ExistsQuery(Func getConnection) 18 | { 19 | _getConnection = getConnection ?? throw new ArgumentNullException(nameof(getConnection)); 20 | 21 | var table = MetadataResolver.TableName(); 22 | 23 | string selectFrom = $"SELECT * FROM {table}"; 24 | _sqlBuilder.Append(selectFrom); 25 | } 26 | 27 | public ExistsQuery Where(Expression> conditionExpression) 28 | { 29 | if (conditionExpression == null) 30 | { 31 | throw new ArgumentNullException("Condition expression must be provided. To select all columns, dont invoke this method."); 32 | } 33 | 34 | string whereCondition = WhereConditionBuilder.FromExpression(conditionExpression); 35 | string where = $"WHERE {whereCondition}"; 36 | 37 | _sqlBuilder.Append(where); 38 | 39 | return this; 40 | } 41 | 42 | public string GetSqlCommand() 43 | { 44 | return _sqlBuilder.GetResult(); 45 | } 46 | 47 | public Task ExecuteAsync() 48 | { 49 | var sqlCommand = GetSqlCommand(); 50 | #if DEBUG 51 | DebugUtil.OutputSqlCommand(sqlCommand); 52 | #endif 53 | NpgsqlConnection connection = _getConnection(); 54 | return connection.ExecuteReaderAsync(sqlCommand, r => r.HasRows); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/QueryCommand/InsertCommand.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using R5.Internals.Extensions.Collections; 3 | using R5.Internals.PostgresMapper.Mappers; 4 | using R5.Internals.PostgresMapper.Models; 5 | using R5.Internals.PostgresMapper.SqlBuilders; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace R5.Internals.PostgresMapper.QueryCommand 13 | { 14 | public class InsertCommand 15 | { 16 | private Func _getConnection { get; } 17 | private ConcatSqlBuilder _sqlBuilder { get; } = new ConcatSqlBuilder(); 18 | 19 | public InsertCommand( 20 | Func getConnection, 21 | List entities) 22 | { 23 | _getConnection = getConnection ?? throw new ArgumentNullException(nameof(getConnection)); 24 | 25 | if (entities.IsNullOrEmpty()) 26 | { 27 | throw new ArgumentNullException(nameof(entities), "At least one entity must be provided for an Insert command."); 28 | } 29 | 30 | List columns = MetadataResolver.TableColumns(); 31 | 32 | AppendInsertInto(columns); 33 | AppendEntityValues(entities, columns); 34 | } 35 | 36 | private void AppendInsertInto(List columns) 37 | { 38 | var table = MetadataResolver.TableName(); 39 | 40 | var columnNames = string.Join(", ", columns.Select(c => c.Name)); 41 | 42 | _sqlBuilder.Append($"INSERT INTO {table} ({columnNames})"); 43 | } 44 | 45 | private void AppendEntityValues(List entities, List columns) 46 | { 47 | var entityValues = entities 48 | .Select(e => GetEntityDbValues(e, columns)); 49 | 50 | var joined = string.Join(", ", entityValues); 51 | 52 | _sqlBuilder.Append($"VALUES {joined}"); 53 | } 54 | 55 | private static string GetEntityDbValues(TEntity entity, List columns) 56 | { 57 | List entityDbValues = columns 58 | .Select(c => c.GetDbValueString(entity)) 59 | .ToList(); 60 | 61 | var joined = string.Join(", ", entityDbValues); 62 | 63 | return $"({joined})"; 64 | } 65 | 66 | public string GetSqlCommand() 67 | { 68 | return _sqlBuilder.GetResult(); 69 | } 70 | 71 | public Task ExecuteAsync() 72 | { 73 | var sqlCommand = GetSqlCommand(); 74 | #if DEBUG 75 | DebugUtil.OutputSqlCommand(sqlCommand); 76 | #endif 77 | NpgsqlConnection connection = _getConnection(); 78 | return connection.ExecuteNonQueryAsync(sqlCommand); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/QueryCommand/TableExistsQuery.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using R5.Internals.PostgresMapper.SqlBuilders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace R5.Internals.PostgresMapper.QueryCommand 9 | { 10 | public class TableExistsQuery 11 | { 12 | private Func _getConnection { get; } 13 | private ConcatSqlBuilder _sqlBuilder { get; } = new ConcatSqlBuilder(); 14 | 15 | public TableExistsQuery(Func getConnection) 16 | { 17 | _getConnection = getConnection ?? throw new ArgumentNullException(nameof(getConnection)); 18 | 19 | var fullTableIdentifier = MetadataResolver.TableName(); 20 | 21 | var split = fullTableIdentifier.Split('.'); 22 | var schema = split[0]; 23 | var tableName = split[1]; 24 | 25 | var innerQuery = "SELECT 1 FROM pg_tables " 26 | + $"WHERE schemaname = '{schema}' AND tablename = '{tableName}'"; 27 | 28 | _sqlBuilder.Append($"SELECT EXISTS({innerQuery})"); 29 | } 30 | 31 | public string GetSqlCommand() 32 | { 33 | return _sqlBuilder.GetResult(); 34 | } 35 | 36 | public Task ExecuteAsync() 37 | { 38 | var sqlCommand = GetSqlCommand(); 39 | #if DEBUG 40 | DebugUtil.OutputSqlCommand(sqlCommand); 41 | #endif 42 | NpgsqlConnection connection = _getConnection(); 43 | return connection.ExecuteReaderAsync(sqlCommand, GetResultFromReader); 44 | } 45 | 46 | private static bool GetResultFromReader(NpgsqlDataReader reader) 47 | { 48 | while (reader.Read()) 49 | { 50 | return (bool)reader.GetValue(0); 51 | } 52 | 53 | throw new InvalidOperationException("Should return or throw before this (har har)"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/QueryCommand/TruncateCommand.cs: -------------------------------------------------------------------------------- 1 | using Npgsql; 2 | using R5.Internals.PostgresMapper.SqlBuilders; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace R5.Internals.PostgresMapper.QueryCommand 7 | { 8 | public class TruncateCommand 9 | { 10 | private Func _getConnection { get; } 11 | private ConcatSqlBuilder _sqlBuilder { get; } = new ConcatSqlBuilder(); 12 | 13 | public TruncateCommand( 14 | Func getConnection) 15 | { 16 | _getConnection = getConnection ?? throw new ArgumentNullException(nameof(getConnection)); 17 | BuildTruncateCommand(); 18 | } 19 | 20 | private void BuildTruncateCommand() 21 | { 22 | _sqlBuilder 23 | .Append($"TRUNCATE {MetadataResolver.TableName()}"); 24 | } 25 | 26 | public string GetSqlCommand() 27 | { 28 | return _sqlBuilder.GetResult(); 29 | } 30 | 31 | public Task ExecuteAsync() 32 | { 33 | var sqlCommand = GetSqlCommand(); 34 | #if DEBUG 35 | DebugUtil.OutputSqlCommand(sqlCommand); 36 | #endif 37 | NpgsqlConnection connection = _getConnection(); 38 | return connection.ExecuteNonQueryAsync(sqlCommand); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/R5.Internals.PostgresMapper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/SqlBuilders/ConcatSqlBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace R5.Internals.PostgresMapper.SqlBuilders 6 | { 7 | public class ConcatSqlBuilder 8 | { 9 | private readonly StringBuilder _sb = new StringBuilder(); 10 | 11 | public ConcatSqlBuilder Append(string sql) 12 | { 13 | var handledForSpacing = sql.Trim() + " "; 14 | _sb.Append(handledForSpacing); 15 | 16 | return this; 17 | } 18 | 19 | public string GetResult(bool omitTerminatingSemiColon = false) 20 | { 21 | var result = _sb.ToString().Trim(); 22 | 23 | if (string.IsNullOrWhiteSpace(result)) 24 | { 25 | throw new InvalidOperationException("SQL result is invalid: cannot be null or empty."); 26 | } 27 | 28 | if (omitTerminatingSemiColon && result.EndsWith(";")) 29 | { 30 | throw new InvalidOperationException("Cannot omit terminating semi-colon because the SQL command already contains one."); 31 | } 32 | 33 | if (!omitTerminatingSemiColon) 34 | { 35 | return result.EndsWith(";") ? result : result + ";"; 36 | } 37 | 38 | return result; 39 | } 40 | 41 | public override string ToString() 42 | { 43 | var result = _sb.ToString().Trim(); 44 | 45 | return string.IsNullOrWhiteSpace(result) ? "[empty]" : result; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/SupportedTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace R5.Internals.PostgresMapper 6 | { 7 | // Only the specified CLR types are supported for class properties. 8 | // On update, make sure to update these mappers to handle them: 9 | // - ToDbValueStringMapper 10 | // - ToPostgresDataTypeMapper 11 | // - DbValueToObjectMapper 12 | internal static class SupportedTypes 13 | { 14 | private static HashSet _types = new HashSet 15 | { 16 | typeof(Guid), 17 | typeof(string), 18 | typeof(int), 19 | typeof(double), 20 | typeof(DateTimeOffset), 21 | typeof(bool) 22 | }; 23 | 24 | internal static bool IsSupported() 25 | { 26 | return _types.Contains(typeof(T)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/System/Tables/InformationSchema.cs: -------------------------------------------------------------------------------- 1 | using R5.Internals.PostgresMapper.Attributes; 2 | using R5.Internals.PostgresMapper.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace R5.Internals.PostgresMapper.System.Tables 8 | { 9 | public static class InformationSchema 10 | { 11 | [Table("information_schema.schemata")] 12 | public class Schemata 13 | { 14 | [Column("schema_name", PostgresDataType.SQL_IDENTIFIER)] 15 | public string SchemaName { get; set; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /R5.Internals/R5.PostgresMapper/todos.txt: -------------------------------------------------------------------------------- 1 | custom exceptions -------------------------------------------------------------------------------- /R5.Internals/Tests/R5.Internals.Caching.Tests/AsyncLazyCacheTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using R5.Internals.Caching.Caches; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace R5.Internals.Caching.Tests 10 | { 11 | public class AsyncLazyCacheTests : IDisposable 12 | { 13 | private AsyncLazyCache Cache; 14 | 15 | // setup 16 | public AsyncLazyCacheTests() 17 | { 18 | var memoryCache = new MemoryCache( 19 | new MemoryCacheOptions()); 20 | 21 | Cache = new AsyncLazyCache(memoryCache); 22 | } 23 | 24 | // teardown 25 | public void Dispose() 26 | { 27 | Cache?.Dispose(); 28 | } 29 | 30 | // Getting Cached Values 31 | 32 | [Fact] 33 | public async Task AlreadyCached_ReturnsCachedValue() 34 | { 35 | var factoryInvoked = 0; 36 | var key = "key"; 37 | 38 | Func> taskFactory1 = () => 39 | { 40 | factoryInvoked++; 41 | return Task.FromResult("value1"); 42 | }; 43 | 44 | Func> taskFactory2 = () => 45 | { 46 | factoryInvoked++; 47 | return Task.FromResult("value2"); 48 | }; 49 | 50 | var resolved1 = await Cache.GetOrCreateAsync(key, taskFactory1); 51 | var resolved2 = await Cache.GetOrCreateAsync(key, taskFactory1); 52 | 53 | Assert.Equal("value1", resolved1); 54 | Assert.Equal(1, factoryInvoked); 55 | 56 | Assert.Equal("value1", resolved2); 57 | Assert.Equal(1, factoryInvoked); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /R5.Internals/Tests/R5.Internals.Caching.Tests/R5.Internals.Caching.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /R5.Internals/Tests/R5.Internals.PostgresMapper.Tests/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace R5.Internals.PostgresMapper.Tests 6 | { 7 | public static class StringExtensions 8 | { 9 | // doesn't remove tabs or other white space chars 10 | public static string RemoveWhiteSpaces(this string str) 11 | { 12 | return str.Replace(" ", String.Empty); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /R5.Internals/Tests/R5.Internals.PostgresMapper.Tests/R5.Internals.PostgresMapper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /R5.Internals/Tests/R5.Internals.PostgresMapper.Tests/Tests/SqlBuilders/WhereConditionBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using R5.Internals.PostgresMapper.Attributes; 2 | using R5.Internals.PostgresMapper.Models; 3 | using R5.Internals.PostgresMapper.SqlBuilders; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace R5.Internals.PostgresMapper.Tests.Tests.SqlBuilders 11 | { 12 | public class WhereConditionBuilderTests 13 | { 14 | [Table("TestEntityTable")] 15 | public class TestEntity 16 | { 17 | [Column("number1", PostgresDataType.INT)] 18 | public int Number1 { get; set; } 19 | 20 | [Column("number2", PostgresDataType.INT)] 21 | public int Number2 { get; set; } 22 | } 23 | 24 | public class WeekInfo 25 | { 26 | public int Season { get; set; } 27 | public int Week { get; set; } 28 | } 29 | 30 | [Fact] 31 | public void Condition_EqualsConstant_WithAndOperator_Success() 32 | { 33 | Expression> expr 34 | = e => e.Number1 == 2019 && e.Number2 == 1; 35 | 36 | string expected = "((number1 = 2019) AND (number2 = 1))"; 37 | 38 | string result = WhereConditionBuilder.FromExpression(expr); 39 | 40 | Assert.Equal( 41 | expected.RemoveWhiteSpaces(), 42 | result.RemoveWhiteSpaces()); 43 | } 44 | 45 | [Fact] 46 | public void Condition_EqualsConstantVarRef_WithAndOperator_Success() 47 | { 48 | int year = 2019; 49 | int week = 1; 50 | 51 | Expression> expr 52 | = e => e.Number1 == year && e.Number2 == week; 53 | 54 | string expected = "((number1 = 2019) AND (number2 = 1))"; 55 | 56 | string result = WhereConditionBuilder.FromExpression(expr); 57 | 58 | Assert.Equal( 59 | expected.RemoveWhiteSpaces(), 60 | result.RemoveWhiteSpaces()); 61 | } 62 | 63 | [Fact] 64 | public void Condition_EqualsObjPropRef_WithAndOperator_Success() 65 | { 66 | var week = new WeekInfo { Season = 2019, Week = 1 }; 67 | 68 | Expression> expr 69 | = e => e.Number1 == week.Season && e.Number2 == week.Week; 70 | 71 | string expected = "((number1 = 2019) AND (number2 = 1))"; 72 | 73 | string result = WhereConditionBuilder.FromExpression(expr); 74 | 75 | Assert.Equal( 76 | expected.RemoveWhiteSpaces(), 77 | result.RemoveWhiteSpaces()); 78 | } 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.0-alpha.2 2 | image: Visual Studio 2017 3 | configuration: Release 4 | 5 | before_build: 6 | - cmd: nuget restore 7 | 8 | build: 9 | project: FFDB.sln 10 | verbosity: minimal 11 | 12 | artifacts: 13 | - path: '**\*.nupkg' --------------------------------------------------------------------------------