diff --git a/src/Client/Features/FreeAgents/List.razor b/src/Client/Features/FreeAgents/List.razor index 1b94534..0d45797 100644 --- a/src/Client/Features/FreeAgents/List.razor +++ b/src/Client/Features/FreeAgents/List.razor @@ -45,7 +45,7 @@ - @context.HighestBid.ToString("C") + @context.HighestBid.ToString("C0") @if (context.CurrentUserIsHighestBidder) { You! diff --git a/src/Server/Features/Error.cshtml b/src/Server/Features/Error.cshtml index 0131455..7e39438 100644 --- a/src/Server/Features/Error.cshtml +++ b/src/Server/Features/Error.cshtml @@ -21,7 +21,6 @@ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.

- diff --git a/src/Server/Features/Teams/Detail.cs b/src/Server/Features/Teams/Detail.cs index 82bc10f..7e44722 100644 --- a/src/Server/Features/Teams/Detail.cs +++ b/src/Server/Features/Teams/Detail.cs @@ -51,8 +51,8 @@ public async Task Handle(DetailQuery request, CancellationToke .SingleAsync(cancellationToken); var rosteredPlayersQuery = _dbContext.Players - .Where(p => p.TeamId == request.TeamId) - .WhereIsRostered(); + .Where(p => p.TeamId == request.TeamId + && p.State == PlayerState.Rostered); var rosteredPlayers = await rosteredPlayersQuery .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(cancellationToken); @@ -61,8 +61,8 @@ public async Task Handle(DetailQuery request, CancellationToke var rosteredPlayersContractValue = await rosteredPlayersQuery.SumAsync(rp => rp.ContractValue, cancellationToken); var unrosteredPlayersQuery = _dbContext.Players - .Where(p => p.TeamId == request.TeamId) - .WhereIsUnrostered(); + .Where(p => p.TeamId == request.TeamId + && p.State == PlayerState.Unrostered); var unrosteredPlayers = await unrosteredPlayersQuery .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(cancellationToken); diff --git a/src/Server/Features/Teams/List.cs b/src/Server/Features/Teams/List.cs index 6813e09..4079027 100644 --- a/src/Server/Features/Teams/List.cs +++ b/src/Server/Features/Teams/List.cs @@ -58,16 +58,16 @@ await Parallel.ForEachAsync(teams, async (team, cancellationToken) => using var dbContext = await _dbContext.CreateDbContextAsync(cancellationToken); var rosteredPlayersQuery = dbContext.Players - .Where(p => p.TeamId == team.Id) - .WhereIsRostered(); + .Where(p => p.TeamId == team.Id + && p.State == PlayerState.Rostered); var rosteredPlayerCount = await rosteredPlayersQuery.CountAsync(cancellationToken); team.RosteredPlayerCount = rosteredPlayerCount.ToString(); var rosteredPlayersContractValue = await rosteredPlayersQuery.SumAsync(rp => rp.ContractValue, cancellationToken); var unrosteredPlayersQuery = dbContext.Players - .Where(p => p.TeamId == team.Id) - .WhereIsUnrostered(); + .Where(p => p.TeamId == team.Id + && p.State == PlayerState.Unrostered); var unrosteredPlayerCount = await unrosteredPlayersQuery.CountAsync(cancellationToken); team.UnrosteredPlayerCount = unrosteredPlayerCount.ToString(); diff --git a/src/Server/Features/Teams/SignPlayer.cs b/src/Server/Features/Teams/SignPlayer.cs index 06e490c..39771eb 100644 --- a/src/Server/Features/Teams/SignPlayer.cs +++ b/src/Server/Features/Teams/SignPlayer.cs @@ -70,7 +70,7 @@ public async Task Handle(SignPlayerCommand request, CancellationToken cance var position = Position.FromName(player.Position); var contractValue = position.GetContractValue(request.YearContractExpires, player.ContractValue); - player.SetToRostered(request.YearContractExpires, contractValue); + player.SignForCurrentTeam(request.YearContractExpires, contractValue); await _dbContext.SaveChangesAsync(cancellationToken); diff --git a/src/Server/Infrastructure/Migrations/20220702193259_AddPlayerState.cs b/src/Server/Infrastructure/Migrations/20220702193259_AddPlayerState.cs index 429bf02..b909c7e 100644 --- a/src/Server/Infrastructure/Migrations/20220702193259_AddPlayerState.cs +++ b/src/Server/Infrastructure/Migrations/20220702193259_AddPlayerState.cs @@ -1,6 +1,5 @@ using DynamoLeagueBlazor.Server.Models; using Microsoft.EntityFrameworkCore.Migrations; -using System.Text; #nullable disable @@ -17,13 +16,12 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: true); // Convert Free Agents to use state. - var sql = new StringBuilder(); - sql.AppendLine("UPDATE Players") - .AppendLine("SET State = 0") - .AppendLine($"WHERE {nameof(Player.EndOfFreeAgency)} >= getdate()") - .AppendLine($"AND {nameof(Player.YearContractExpires)} < YEAR(getdate())"); + var sql = $@"UPDATE Players + SET State = 1 + WHERE {nameof(Player.EndOfFreeAgency)} >= getdate() + AND {nameof(Player.YearContractExpires)} < YEAR(getdate())"; - migrationBuilder.Sql(sql.ToString()); + migrationBuilder.Sql(sql); } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/src/Server/Infrastructure/Migrations/20220703013416_AddPlayerStateOfferMatchingToUnsigned.cs b/src/Server/Infrastructure/Migrations/20220703013416_AddPlayerStateOfferMatchingToUnsigned.cs index 2420bff..c245123 100644 --- a/src/Server/Infrastructure/Migrations/20220703013416_AddPlayerStateOfferMatchingToUnsigned.cs +++ b/src/Server/Infrastructure/Migrations/20220703013416_AddPlayerStateOfferMatchingToUnsigned.cs @@ -1,6 +1,5 @@ using DynamoLeagueBlazor.Server.Models; using Microsoft.EntityFrameworkCore.Migrations; -using System.Text; #nullable disable @@ -11,22 +10,20 @@ public partial class AddPlayerStateOfferMatchingToUnsigned : Migration protected override void Up(MigrationBuilder migrationBuilder) { // Convert Offer Matches to use state. - var sql = new StringBuilder(); - sql.AppendLine("UPDATE Players") - .AppendLine("SET State = 1") - .AppendLine($"WHERE DATEADD(day, 3, {nameof(Player.EndOfFreeAgency)}) <= getdate()"); + var sql = $@"UPDATE Players + SET State = 1 + WHERE DATEADD(day, 3, {nameof(Player.EndOfFreeAgency)}) <= getdate()"; - migrationBuilder.Sql(sql.ToString()); + migrationBuilder.Sql(sql); } protected override void Down(MigrationBuilder migrationBuilder) { - var sql = new StringBuilder(); - sql.AppendLine("UPDATE Players") - .AppendLine("SET State = NULL") - .AppendLine($"WHERE STATE = 1"); + var sql = @"UPDATE Players + SET State = NULL + WHERE STATE = 1"; - migrationBuilder.Sql(sql.ToString()); + migrationBuilder.Sql(sql); } } } diff --git a/src/Server/Infrastructure/Migrations/20220703022908_AddPlayerStateUnsignedToRostered.Designer.cs b/src/Server/Infrastructure/Migrations/20220703022908_AddPlayerStateUnsignedToRostered.Designer.cs new file mode 100644 index 0000000..acbd5ce --- /dev/null +++ b/src/Server/Infrastructure/Migrations/20220703022908_AddPlayerStateUnsignedToRostered.Designer.cs @@ -0,0 +1,637 @@ +// +using System; +using DynamoLeagueBlazor.Server.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DynamoLeagueBlazor.Server.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20220703022908_AddPlayerStateUnsignedToRostered")] + partial class AddPlayerStateUnsignedToRostered + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.Key", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Algorithm") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DataProtected") + .HasColumnType("bit"); + + b.Property("IsX509Certificate") + .HasColumnType("bit"); + + b.Property("Use") + .HasColumnType("nvarchar(450)"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Use"); + + b.ToTable("Keys"); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ConsumedTime") + .HasColumnType("datetime2"); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Key"); + + b.HasIndex("ConsumedTime"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("bit"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.HasIndex("TeamId"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Bid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Amount") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("PlayerId") + .HasColumnType("int"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("TeamId"); + + b.ToTable("Bids"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Fine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Amount") + .HasColumnType("decimal(18,0)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("PlayerId") + .HasColumnType("int"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("bit"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("TeamId"); + + b.ToTable("Fines"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ContractValue") + .HasColumnType("int"); + + b.Property("EndOfFreeAgency") + .HasColumnType("datetime2"); + + b.Property("HeadShotUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Position") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Rostered") + .HasColumnType("bit"); + + b.Property("State") + .HasColumnType("int"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.Property("YearAcquired") + .HasColumnType("int"); + + b.Property("YearContractExpires") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Team", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("LogoUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Teams"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Bid", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Player", "Player") + .WithMany("Bids") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Fine", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Player", "Player") + .WithMany("Fines") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany("Fines") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Player", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany("Players") + .HasForeignKey("TeamId"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Player", b => + { + b.Navigation("Bids"); + + b.Navigation("Fines"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Team", b => + { + b.Navigation("Fines"); + + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Server/Infrastructure/Migrations/20220703022908_AddPlayerStateUnsignedToRostered.cs b/src/Server/Infrastructure/Migrations/20220703022908_AddPlayerStateUnsignedToRostered.cs new file mode 100644 index 0000000..bfa1e5d --- /dev/null +++ b/src/Server/Infrastructure/Migrations/20220703022908_AddPlayerStateUnsignedToRostered.cs @@ -0,0 +1,29 @@ +using DynamoLeagueBlazor.Server.Models; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DynamoLeagueBlazor.Server.Infrastructure.Migrations +{ + public partial class AddPlayerStateUnsignedToRostered : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // Convert Unsigned to use state. + var sql = $@"UPDATE Players + SET State = 2 + WHERE DATEADD(day, 3, {nameof(Player.EndOfFreeAgency)}) >= getdate()"; + + migrationBuilder.Sql(sql); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var sql = @"UPDATE Players + SET State = NULL + WHERE STATE = 2"; + + migrationBuilder.Sql(sql); + } + } +} diff --git a/src/Server/Models/Player.cs b/src/Server/Models/Player.cs index d9b7495..4249667 100644 --- a/src/Server/Models/Player.cs +++ b/src/Server/Models/Player.cs @@ -5,13 +5,13 @@ namespace DynamoLeagueBlazor.Server.Models; public record Player : BaseEntity { private readonly StateMachine _machine; - //private readonly StateMachine.TriggerWithParameters _rosteredTrigger; + private readonly StateMachine.TriggerWithParameters _rosteredTrigger; //private readonly StateMachine.TriggerWithParameters _freeAgentTrigger; public Player() { _machine = new(() => State, state => State = state); - //_rosteredTrigger = _machine.SetTriggerParameters(PlayerStateTrigger.SignedByTeam); + _rosteredTrigger = _machine.SetTriggerParameters(PlayerStateTrigger.SignedByTeam); //_freeAgentTrigger = _machine.SetTriggerParameters(PlayerStateTrigger.NewSeasonStarted); _machine.Configure(PlayerState.FreeAgent) @@ -21,6 +21,14 @@ public Player() .Permit(PlayerStateTrigger.OfferMatchedByTeam, PlayerState.Unsigned) .Permit(PlayerStateTrigger.MatchExpired, PlayerState.Unsigned) .OnExit(() => SetToUnsigned()); + + _machine.Configure(PlayerState.Unsigned) + .Permit(PlayerStateTrigger.SignedByTeam, PlayerState.Rostered); + + _machine.Configure(PlayerState.Rostered) + .OnEntryFrom(_rosteredTrigger, (yearContractExpires, contractValue) => SetToRostered(yearContractExpires, contractValue)) + .Permit(PlayerStateTrigger.DroppedByTeam, PlayerState.Unrostered) + .Permit(PlayerStateTrigger.NewSeasonStarted, PlayerState.FreeAgent); } public Player(string name, string position, string headShotUrl) : this() @@ -36,7 +44,7 @@ public Player(string name, string position, string headShotUrl) : this() public int? YearContractExpires { get; set; } public int ContractValue { get; set; } public int YearAcquired { get; set; } - public bool Rostered { get; set; } // TODO: Remove this when rostered is a state. + public bool Rostered { get; set; } // TODO: Remove this when all needs for rostered are done. public int? TeamId { get; set; } public DateTime? EndOfFreeAgency { get; set; } public PlayerState? State { get; set; } = PlayerState.Unsigned; @@ -48,33 +56,21 @@ public Player(string name, string position, string headShotUrl) : this() private enum PlayerStateTrigger { NewSeasonStarted, BiddingEnded, OfferMatchedByTeam, MatchExpired, SignedByTeam, DroppedByTeam } public enum PlayerState { FreeAgent, OfferMatching, Unsigned, Rostered, Unrostered } - public Player SetToRostered(int yearContractExpires, int contractValue) - { - Rostered = true; - YearContractExpires = yearContractExpires; - EndOfFreeAgency = null; - ContractValue = contractValue; - - return this; - } - public Player SetToUnrostered() { - Rostered = false; EndOfFreeAgency = null; return this; } + public void EndBidding() => _machine.Fire(PlayerStateTrigger.BiddingEnded); + public void MatchOffer() => _machine.Fire(PlayerStateTrigger.OfferMatchedByTeam); public void ExpireMatch() => _machine.Fire(PlayerStateTrigger.MatchExpired); - public void EndBidding() => _machine.Fire(PlayerStateTrigger.BiddingEnded); - private Player SetToUnsigned() { - Rostered = false; YearContractExpires = null; EndOfFreeAgency = null; ContractValue = Bids.FindHighestBid()?.Amount ?? Bid.MinimumAmount; @@ -83,16 +79,18 @@ private Player SetToUnsigned() return this; } - public Player SetToFreeAgent(DateTime endOfFreeAgency) - { - EndOfFreeAgency = endOfFreeAgency; + public void SignForCurrentTeam(int yearContractExpires, int contractValue) => _machine.Fire(_rosteredTrigger, yearContractExpires, contractValue); - return this; + private void SetToRostered(int yearContractExpires, int contractValue) + { + YearContractExpires = yearContractExpires; + ContractValue = contractValue; + EndOfFreeAgency = null; } - public Player SetToOfferMatching() + public Player SetToFreeAgent(DateTime endOfFreeAgency) { - _machine.Fire(PlayerStateTrigger.BiddingEnded); + EndOfFreeAgency = endOfFreeAgency; return this; } @@ -147,14 +145,11 @@ private bool IsEligibleForFreeAgencyExtension(int teamId) public static class PlayerExtensions { - public static IQueryable WhereIsRostered(this IQueryable players) - => players.Where(p => p.Rostered - && p.YearContractExpires >= DateTime.Today.Year - && p.EndOfFreeAgency == null); - public static IQueryable WhereIsUnrostered(this IQueryable players) - => players.Where(p => p.Rostered == false - && p.YearContractExpires != null - && p.EndOfFreeAgency == null); + // TODO: For reference when doing unrostering/dropping players. Remove when that state is added. + //public static IQueryable WhereIsUnrostered(this IQueryable players) + // => players.Where(p => p.Rostered == false + // && p.YearContractExpires != null + // && p.EndOfFreeAgency == null); public static IQueryable WhereIsEligibleForFreeAgency(this IQueryable players) => players.Where(p => p.YearContractExpires < DateTime.Today.Year); diff --git a/src/Tests/Features/FreeAgents/DetailTests.cs b/src/Tests/Features/FreeAgents/DetailTests.cs index 17e4e1b..1146350 100644 --- a/src/Tests/Features/FreeAgents/DetailTests.cs +++ b/src/Tests/Features/FreeAgents/DetailTests.cs @@ -11,11 +11,9 @@ public async Task GivenUnauthenticatedUser_ThenDoesNotAllowAccess() var application = CreateUnauthenticatedApplication(); var client = application.CreateClient(); - var stubFreeAgent = CreateFakePlayer() - .SetToRostered(DateTime.Today.AddYears(-1).Year, int.MaxValue) - .SetToFreeAgent(DateTime.MaxValue); - await application.AddAsync(stubFreeAgent); - var endpoint = FreeAgentDetailFactory.Create(stubFreeAgent.Id); + var stubPlayer = CreateFakePlayer(); + await application.AddAsync(stubPlayer); + var endpoint = FreeAgentDetailFactory.Create(stubPlayer.Id); var response = await client.GetAsync(endpoint); @@ -30,9 +28,7 @@ public async Task GivenAnyAuthenticatedUser_WhenGivenValidPlayerId_ThenReturnsEx var mockTeam = CreateFakeTeam(); await application.AddAsync(mockTeam); - var mockFreeAgent = CreateFakePlayer() - .SetToRostered(DateTime.Today.AddYears(-1).Year, int.MaxValue) - .SetToFreeAgent(DateTime.MaxValue); + var mockFreeAgent = CreateFakePlayer(); mockFreeAgent.TeamId = mockTeam.Id; await application.AddAsync(mockFreeAgent); diff --git a/src/Tests/Features/FreeAgents/ListTests.cs b/src/Tests/Features/FreeAgents/ListTests.cs index 5ced2e4..da08993 100644 --- a/src/Tests/Features/FreeAgents/ListTests.cs +++ b/src/Tests/Features/FreeAgents/ListTests.cs @@ -40,7 +40,7 @@ public async Task GivenAnyAuthenticatedUser_WhenThereIsOnePlayerWhoIsAFreeAgent_ var mockPlayer = CreateFakePlayer(); mockPlayer.TeamId = mockTeam.Id; - mockPlayer.SetToRostered(DateTime.MinValue.Year, int.MaxValue); + mockPlayer.SignForCurrentTeam(DateTime.MinValue.Year, int.MaxValue); var biddingEnds = DateTime.MaxValue; mockPlayer.SetToFreeAgent(biddingEnds); await application.AddAsync(mockPlayer); @@ -77,7 +77,7 @@ public async Task GivenAnyAuthenticatedUser_WhenAFreeAgentHasABidByAnotherTeam_T var mockPlayer = CreateFakePlayer(); mockPlayer.TeamId = mockTeam.Id; - mockPlayer.SetToRostered(DateTime.MinValue.Year, int.MaxValue); + mockPlayer.SignForCurrentTeam(DateTime.MinValue.Year, int.MaxValue); var biddingEnds = DateTime.MaxValue; mockPlayer.SetToFreeAgent(biddingEnds); await application.AddAsync(mockPlayer); diff --git a/src/Tests/Features/OfferMatching/ListTests.cs b/src/Tests/Features/OfferMatching/ListTests.cs index 7090acf..1ac6373 100644 --- a/src/Tests/Features/OfferMatching/ListTests.cs +++ b/src/Tests/Features/OfferMatching/ListTests.cs @@ -87,8 +87,7 @@ public async Task GivenAnyAuthenticatedUser_WhenPlayerIsMatchedAndHasBids_ThenPl await client.PostAsJsonAsync(OfferMatchingListRouteFactory.Uri, request); var result = await application.FirstOrDefaultAsync(); - result!.Rostered.Should().Be(false); - result.YearContractExpires.Should().Be(null); + result!.YearContractExpires.Should().Be(null); result.EndOfFreeAgency.Should().Be(null); result.YearAcquired.Should().Be(DateTime.Today.Year); result.ContractValue.Should().Be(int.MaxValue); diff --git a/src/Tests/Features/Teams/DetailTests.cs b/src/Tests/Features/Teams/DetailTests.cs index 3dcf15f..61fc650 100644 --- a/src/Tests/Features/Teams/DetailTests.cs +++ b/src/Tests/Features/Teams/DetailTests.cs @@ -30,7 +30,7 @@ public async Task GivenAnyAuthenticatedUser_WhenGivenValidTeamId_ThenReturnsExpe var mockRosteredPlayer = CreateFakePlayer(); mockRosteredPlayer.TeamId = stubTeam.Id; - mockRosteredPlayer.SetToRostered(DateTime.MaxValue.Year, 1); + mockRosteredPlayer.SignForCurrentTeam(DateTime.MaxValue.Year, 1); mockRosteredPlayer.State = PlayerState.Rostered; await application.AddAsync(mockRosteredPlayer); diff --git a/src/Tests/Features/Teams/ListTests.cs b/src/Tests/Features/Teams/ListTests.cs index 8b4666f..dc93deb 100644 --- a/src/Tests/Features/Teams/ListTests.cs +++ b/src/Tests/Features/Teams/ListTests.cs @@ -23,7 +23,7 @@ public async Task GivenAnyAuthenticatedUser_WhenThereIsOneTeam_ThenReturnsOneTea var mockTeam = CreateFakeTeam(); await application.AddAsync(mockTeam); - var rosteredPlayer = CreateFakePlayer().SetToRostered(DateTime.MaxValue.Year, int.MaxValue); + var rosteredPlayer = CreateFakePlayer(); rosteredPlayer.TeamId = mockTeam.Id; rosteredPlayer.State = PlayerState.Rostered; await application.AddAsync(rosteredPlayer); diff --git a/src/Tests/Features/Teams/SignPlayerTests.cs b/src/Tests/Features/Teams/SignPlayerTests.cs index 4aae67e..be12c63 100644 --- a/src/Tests/Features/Teams/SignPlayerTests.cs +++ b/src/Tests/Features/Teams/SignPlayerTests.cs @@ -47,7 +47,6 @@ public async Task GivenAuthenticatedAdmin_ThenSignsPlayer() result.Should().NotBeNull(); result!.YearContractExpires.Should().Be(request.YearContractExpires); - result.Rostered.Should().BeTrue(); result.EndOfFreeAgency.Should().BeNull(); var position = Position.FromName(player.Position); var expectedContractValue = position.GetContractValue(request.YearContractExpires, player.ContractValue); diff --git a/src/Tests/Models/PlayerTests.cs b/src/Tests/Models/PlayerTests.cs index 1bd800a..74492aa 100644 --- a/src/Tests/Models/PlayerTests.cs +++ b/src/Tests/Models/PlayerTests.cs @@ -1,5 +1,4 @@ using DynamoLeagueBlazor.Server.Models; -using static DynamoLeagueBlazor.Server.Models.Player; namespace DynamoLeagueBlazor.Tests.Models; @@ -57,6 +56,17 @@ public void GivenAnOfferMatchingPlayer_WhenMatchIsExpiring_ThenMovesToUnsigned() offerMatchingPlayer.State.Should().Be(PlayerState.Unsigned); } + [Fact] + public void GivenAnUnsignedPlayer_WhenATeamSignsThePlayer_ThenMovesToRostered() + { + var offerMatchingPlayer = CreateFakePlayer(); + offerMatchingPlayer.State = PlayerState.Unsigned; + + offerMatchingPlayer.SignForCurrentTeam(int.MaxValue, int.MaxValue); + + offerMatchingPlayer.State.Should().Be(PlayerState.Rostered); + } + //[Fact] //public void GivenABrandNewPlayer_ThenCanGoThroughTheCompleteLifetime() //{