Skip to content

Commit

Permalink
Add surface controller and save stubs for team owners to link player …
Browse files Browse the repository at this point in the history
…identies #627
  • Loading branch information
sussexrick committed Sep 7, 2024
1 parent 8c7ad49 commit 6c67005
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 17 deletions.
44 changes: 32 additions & 12 deletions Stoolball.Data.Abstractions/IPlayerRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,61 @@ namespace Stoolball.Data.Abstractions
public interface IPlayerRepository
{
/// <summary>
/// Finds an existing player identity or creates it if it is not found
/// Finds an existing player identity or creates it if it is not found.
/// </summary>
/// <returns>The <see cref="PlayerIdentity"/> of the created or matched player identity</returns>
/// <returns>The <see cref="PlayerIdentity"/> of the created or matched player identity.</returns>
Task<PlayerIdentity> CreateOrMatchPlayerIdentity(PlayerIdentity playerIdentity, Guid memberKey, string memberName, IDbTransaction transaction);

/// <summary>
/// Sets the MemberKey on a player to the supplied MemberKey, or moves the identities from <c>player</c> to an existing player if one is already linked.
/// </summary>
/// <param name="player">The player to update</param>
/// <param name="memberKey">The MemberKey to associate to the member, and the key of the member to update</param>
/// <param name="memberName">The name of the member making the update</param>
/// <param name="player">The player to update.</param>
/// <param name="memberKey">The MemberKey to associate to the member, and the key of the member to update.</param>
/// <param name="memberName">The name of the member making the update.</param>
/// <returns>The combined player, with updated <c>PlayerId</c>, <c>PlayerRoute</c> and <c>MemberKey</c> if appropriate.</returns>
Task<Player> LinkPlayerToMemberAccount(Player player, Guid memberKey, string memberName);

/// <summary>
/// Links a player identity to an existing player.
/// </summary>
/// <param name="player">The player to link the identity to.</param>
/// <param name="playerIdentity">The player identity to link to the player.</param>
/// <param name="linkedBy">The role that authorises the current member to make the update.</param>
/// <param name="memberKey">The MemberKey of the member making the update.</param>
/// <param name="memberName">The name of the member making the update.</param>
/// <returns></returns>
Task<Player> LinkPlayerIdentity(Player player, PlayerIdentity playerIdentity, PlayerIdentityLinkedBy linkedBy, Guid memberKey, string memberName);

/// <summary>
/// Removes the player identity from its player, creating a new player not linked to the member. Or for the last player identity linked to a member, removes the MemberKey from the player.
/// </summary>
/// <param name="playerIdentity">The player identity to unlink</param>
/// <param name="memberKey">The member to unlink the player identity from</param>
/// <param name="memberName">The name of the member making the update</param>
/// <param name="playerIdentity">The player identity to unlink.</param>
/// <param name="memberKey">The member to unlink the player identity from.</param>
/// <param name="memberName">The name of the member making the update.</param>
/// <returns></returns>
Task UnlinkPlayerIdentityFromMemberAccount(PlayerIdentity playerIdentity, Guid memberKey, string memberName);

/// <summary>
/// Removes the player identity from its player, creating a new player.
/// </summary>
/// <param name="playerIdentity">The player identity to remove from the player.</param>
/// <param name="memberKey">The MemberKey of the member making the update.</param>
/// <param name="memberName">The name of the member making the update.</param>
/// <returns></returns>
Task UnlinkPlayerIdentity(PlayerIdentity playerIdentity, Guid memberKey, string memberName);

/// <summary>
/// <see cref="LinkPlayerToMemberAccount"/>, <see cref="UnlinkPlayerIdentityFromMemberAccount"/> and <see cref="UpdatePlayerIdentity"/>. All leave updates to statistics and cleaning up unused resources incomplete, to be done asynchronously. This completes the work.
/// </summary>
/// <returns></returns>
Task ProcessAsyncUpdatesForPlayers();

/// <summary>
/// Updates the name of a player identity
/// Updates the name of a player identity.
/// </summary>
/// <param name="playerIdentity">The player identity to update</param>
/// <param name="memberKey">The key of the member making the update</param>
/// <param name="memberName">The name of the member making the update</param>
/// <param name="playerIdentity">The player identity to update.</param>
/// <param name="memberKey">The key of the member making the update.</param>
/// <param name="memberName">The name of the member making the update.</param>
/// <returns></returns>
Task<RepositoryResult<PlayerIdentityUpdateResult, PlayerIdentity>> UpdatePlayerIdentity(PlayerIdentity playerIdentity, Guid memberKey, string memberName);
}
Expand Down
13 changes: 13 additions & 0 deletions Stoolball.Data.SqlServer/SqlServerPlayerRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ await _auditRepository.CreateAudit(new AuditRecord
}
}

/// <inheritdoc />
public Task<Player> LinkPlayerIdentity(Player player, PlayerIdentity playerIdentity, PlayerIdentityLinkedBy linkedBy, Guid memberKey, string memberName)
{
throw new NotImplementedException();
}


private async Task RedirectPlayerRoute(string routeBefore, string routeAfter, IDbTransaction transaction)
{
await _redirectsRepository.InsertRedirect(routeBefore, routeAfter, null, transaction);
Expand Down Expand Up @@ -406,6 +413,12 @@ await _auditRepository.CreateAudit(new AuditRecord
}
}

/// <inheritdoc />
public Task UnlinkPlayerIdentity(PlayerIdentity playerIdentity, Guid memberKey, string memberName)
{
throw new NotImplementedException();
}

/// <inheritdoc />
public async Task ProcessAsyncUpdatesForPlayers()
{
Expand Down
100 changes: 100 additions & 0 deletions Stoolball.Web/Teams/LinkedPlayersForIdentitySurfaceController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Stoolball.Data.Abstractions;
using Stoolball.Security;
using Stoolball.Statistics;
using Stoolball.Teams;
using Stoolball.Web.Security;
using Stoolball.Web.Statistics.Models;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.Common.Filters;
using Umbraco.Cms.Web.Website.Controllers;

namespace Stoolball.Web.Teams
{
public class LinkedPlayersForIdentitySurfaceController : SurfaceController
{
private readonly IMemberManager _memberManager;
private readonly IAuthorizationPolicy<Team> _authorizationPolicy;
private readonly IPlayerDataSource _playerDataSource;
private readonly IPlayerRepository _playerRepository;
private readonly IPlayerCacheInvalidator _playerCacheClearer;

public LinkedPlayersForIdentitySurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory umbracoDatabaseFactory,
ServiceContext serviceContext, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider,
IMemberManager memberManager,
IAuthorizationPolicy<Team> authorizationPolicy,
IPlayerDataSource playerDataSource,
IPlayerRepository playerRepository,
IPlayerCacheInvalidator playerCacheClearer)
: base(umbracoContextAccessor, umbracoDatabaseFactory, serviceContext, appCaches, profilingLogger, publishedUrlProvider)
{
_memberManager = memberManager ?? throw new ArgumentNullException(nameof(memberManager));
_authorizationPolicy = authorizationPolicy ?? throw new ArgumentNullException(nameof(authorizationPolicy));
_playerDataSource = playerDataSource ?? throw new ArgumentNullException(nameof(playerDataSource));
_playerRepository = playerRepository ?? throw new ArgumentNullException(nameof(playerRepository));
_playerCacheClearer = playerCacheClearer ?? throw new ArgumentNullException(nameof(playerCacheClearer));
}


[HttpPost]
[ValidateAntiForgeryToken]
[ValidateUmbracoFormRouteString]
[ContentSecurityPolicy]
public async Task<IActionResult> UpdateLinkedPlayers(LinkedPlayersFormData formData)
{
var model = new LinkedPlayersViewModel(CurrentPage)
{
ContextIdentity = await _playerDataSource.ReadPlayerIdentityByRoute(Request.Path)
};

if (model.ContextIdentity?.Team == null || model.ContextIdentity?.Player == null)
{
return NotFound();
}
else
{
model.Authorization.CurrentMemberIsAuthorized = await _authorizationPolicy.IsAuthorized(model.ContextIdentity.Team);
if (!model.Authorization.CurrentMemberIsAuthorized[AuthorizedAction.EditTeam])
{
return Forbid();
}

model.Player = await _playerDataSource.ReadPlayerByRoute(model.ContextIdentity.Player!.PlayerRoute!);

var previousIdentities = model.Player!.PlayerIdentities.Select(id => id.PlayerIdentityId!.Value).ToList();
var submittedIdentities = formData.PlayerIdentities.Select(id => id.PlayerIdentityId!.Value).ToList();
var identitiesToLink = submittedIdentities.Where(id => !previousIdentities.Contains(id));
var identitiesToKeep = submittedIdentities.Where(id => previousIdentities.Contains(id));
var identitiesToUnlink = model.Player!.PlayerIdentities.Where(id => id.LinkedBy == PlayerIdentityLinkedBy.ClubOrTeam && !identitiesToKeep.Contains(id.PlayerIdentityId!.Value));

var currentMember = await _memberManager.GetCurrentMemberAsync();
foreach (var identity in identitiesToLink)
{
await _playerRepository.LinkPlayerIdentity(model.Player, formData.PlayerIdentities.Single(id => id.PlayerIdentityId == identity), PlayerIdentityLinkedBy.ClubOrTeam, currentMember.Key, currentMember.Name);
}

foreach (var identity in identitiesToUnlink)
{
await _playerRepository.UnlinkPlayerIdentity(identity, currentMember.Key, currentMember.Name);
}

if (identitiesToUnlink.Any())
{
_playerCacheClearer.InvalidateCacheForPlayer(model.Player);
}

var redirectToUrl = model.ContextIdentity.Team.TeamRoute + "/edit/players";
return Redirect(redirectToUrl);
}
}
}
}
9 changes: 6 additions & 3 deletions Stoolball.Web/Views/LinkedPlayersForIdentity.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
their statistics may be spread over multiple players.</p>

<p>You should combine all the player pages into one, to tidy up the list of players in your team and to see their true statistics. </p>
<partial name="_LinkedPlayers" model="Model" />
<button class="btn btn-primary" type="submit">Save players</button>
<p><a href="@Model.ContextIdentity?.Team?.TeamRoute/edit/players/@Model.ContextIdentity?.RouteSegment">Cancel</a></p>
using (Html.BeginUmbracoForm<LinkedPlayersForIdentitySurfaceController>(nameof(LinkedPlayersForIdentitySurfaceController.UpdateLinkedPlayers)))
{
<partial name="_LinkedPlayers" model="Model" />
<button class="btn btn-primary" type="submit">Save players</button>
<p><a href="@Model.ContextIdentity?.Team?.TeamRoute/edit/players/@Model.ContextIdentity?.RouteSegment">Cancel</a></p>
}
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions Stoolball.Web/Views/PlayerIdentityActions.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<dl>
<dt><a href="@Model.PlayerIdentity?.Team?.TeamRoute/edit/players/@Model.PlayerIdentity?.RouteSegment/rename">Rename @Model.PlayerIdentity?.PlayerIdentityName</a></dt>
<dd>Rename @Model.PlayerIdentity?.PlayerIdentityName if the name is incomplete or incorrect and the correct name is not also listed.</dd>
<dt><a href="@Model.PlayerIdentity?.Team?.TeamRoute/edit/players/@Model.PlayerIdentity?.RouteSegment/statistics">Link @Model.PlayerIdentity?.PlayerIdentityName to the same player listed under another name</a></dt>
<dd>If the same player is listed multiple times, combine the player pages to see their true statistics.</dd>
@* <dt><a href="@Model.PlayerIdentity?.Team?.TeamRoute/edit/players/@Model.PlayerIdentity?.RouteSegment/statistics">Link @Model.PlayerIdentity?.PlayerIdentityName to the same player listed under another name</a></dt>
<dd>If the same player is listed multiple times, combine the player pages to see their true statistics.</dd> *@
</dl>
}
else
Expand Down

0 comments on commit 6c67005

Please sign in to comment.