This commit is contained in:
2023-01-14 11:02:28 -06:00
15 changed files with 359 additions and 361 deletions

View File

@@ -59,10 +59,14 @@ public class SessionRepository : ISessionRepository
{
session.Board.Move(move.PieceFromHand.Value, move.To);
}
else
else if (move.From != null)
{
session.Board.Move(move.From, move.To, false);
}
else
{
throw new InvalidOperationException($"Corrupt data during {nameof(ReadSession)}");
}
}
return session;
}

View File

@@ -47,7 +47,7 @@ public class ShogiUserClaimsTransformer : IShogiUserClaimsTransformer
private async Task<ClaimsPrincipal> CreateClaimsFromMicrosoftPrincipal(ClaimsPrincipal principal)
{
var id = principal.GetMsalAccountId();
var id = principal.GetMicrosoftUserId();
if (string.IsNullOrWhiteSpace(id))
{
throw new UnauthorizedAccessException("Found MSAL claims but no preferred_username.");

View File

@@ -26,7 +26,7 @@ BEGIN
piece.[Name] as PieceFromHand
FROM [session].[Move] mv
INNER JOIN [session].[Session] sess ON sess.Id = mv.SessionId
RIGHT JOIN [session].Piece piece on piece.Id = mv.PieceIdFromHand
LEFT JOIN [session].Piece piece on piece.Id = mv.PieceIdFromHand
WHERE sess.[Name] = @Name;
COMMIT

View File

@@ -1,9 +1,9 @@
@*<CascadingAuthenticationState>*@
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
@*<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<Authorizing>
Authorizing!!
</Authorizing>
@@ -17,7 +17,7 @@
<p role="alert">You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>*@
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
@@ -28,4 +28,4 @@
</NotFound>
</Router>
@*</CascadingAuthenticationState>*@
</CascadingAuthenticationState>

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Shogi.UI.Pages.Home.Api;
using Shogi.UI.Shared;
@@ -10,7 +11,7 @@ public class AccountManager
private readonly IShogiApi shogiApi;
private readonly IConfiguration configuration;
private readonly ILocalStorage localStorage;
//private readonly AuthenticationStateProvider authState;
private readonly AuthenticationStateProvider authState;
private readonly NavigationManager navigation;
private readonly ShogiSocket shogiSocket;
@@ -18,7 +19,7 @@ public class AccountManager
AccountState accountState,
IShogiApi unauthenticatedClient,
IConfiguration configuration,
//AuthenticationStateProvider authState,
AuthenticationStateProvider authState,
ILocalStorage localStorage,
NavigationManager navigation,
ShogiSocket shogiSocket)
@@ -26,7 +27,7 @@ public class AccountManager
this.accountState = accountState;
this.shogiApi = unauthenticatedClient;
this.configuration = configuration;
//this.authState = authState;
this.authState = authState;
this.localStorage = localStorage;
this.navigation = navigation;
this.shogiSocket = shogiSocket;
@@ -53,29 +54,28 @@ public class AccountManager
public async Task LoginWithMicrosoftAccount()
{
throw new NotImplementedException();
//var state = await authState.GetAuthenticationStateAsync();
var state = await authState.GetAuthenticationStateAsync();
//if (state.User?.Identity?.Name == null || state.User?.Identity?.IsAuthenticated != true)
//{
// navigation.NavigateTo("authentication/login");
// return;
//}
if (state.User?.Identity?.Name == null || state.User?.Identity?.IsAuthenticated != true)
{
navigation.NavigateTo("authentication/login");
return;
}
//var id = state.User.Identity.Name;
//var socketToken = await shogiApi.GetToken();
//if (socketToken.HasValue)
//{
// User = new User
// {
// DisplayName = id,
// Id = id,
// OneTimeSocketToken = socketToken.Value
// };
var response = await shogiApi.GetToken();
if (response != null)
{
User = new User
{
DisplayName = response.DisplayName,
Id = response.UserId,
OneTimeSocketToken = response.OneTimeToken,
WhichAccountPlatform = WhichAccountPlatform.Microsoft,
};
// await ConnectToSocketAsync();
// await localStorage.SetAccountPlatform(WhichAccountPlatform.Microsoft);
// }
await shogiSocket.OpenAsync(User.OneTimeSocketToken.ToString());
await localStorage.SetAccountPlatform(WhichAccountPlatform.Microsoft);
}
}
/// <summary>

View File

@@ -1,9 +1,9 @@
using Shogi.UI.Shared;
namespace Shogi.UI.Pages.Home.Account
namespace Shogi.UI.Pages.Home.Account;
public static class LocalStorageExtensions
{
public static class LocalStorageExtensions
{
private const string AccountPlatform = "AccountPlatform";
public static Task<WhichAccountPlatform?> GetAccountPlatform(this ILocalStorage self)
@@ -20,5 +20,4 @@ namespace Shogi.UI.Pages.Home.Account
{
return self.Delete(AccountPlatform).AsTask();
}
}
}

View File

@@ -1,7 +1,6 @@
namespace Shogi.UI.Pages.Home.Account
namespace Shogi.UI.Pages.Home.Account;
public class LoginEventArgs : EventArgs
{
public class LoginEventArgs : EventArgs
{
public User? User { get; set; }
}
}

View File

@@ -10,6 +10,6 @@ public interface IShogiApi
Task<ReadSessionsPlayerCountResponse?> GetSessionsPlayerCount();
Task<CreateTokenResponse?> GetToken();
Task GuestLogout();
Task PostMove(string sessionName, MovePieceCommand move);
Task Move(string sessionName, MovePieceCommand move);
Task<HttpStatusCode> PostSession(string name, bool isPrivate);
}

View File

@@ -63,9 +63,9 @@ namespace Shogi.UI.Pages.Home.Api
return response;
}
public async Task PostMove(string sessionName, MovePieceCommand command)
public async Task Move(string sessionName, MovePieceCommand command)
{
await this.HttpClient.PostAsJsonAsync($"Sessions{sessionName}/Move", command);
await this.HttpClient.PatchAsync($"Sessions/{sessionName}/Move", JsonContent.Create(command));
}
public async Task<HttpStatusCode> PostSession(string name, bool isPrivate)

View File

@@ -46,7 +46,7 @@
<span>I</span>
</div>
<!-- Promote prompt -->
<div class="promote-prompt">
<div class="promote-prompt" data-visible="@PromotePrompt.IsVisible">
<p>Do you wish to promote?</p>
<div>
<button type="button">Yes</button>
@@ -114,6 +114,7 @@
: this.session.BoardState.Player2Hand;
}
}
bool IsMyTurn => session?.BoardState.WhoseTurn == Perspective;
string? selectedPosition;
WhichPiece? selectedPiece;
@@ -138,21 +139,24 @@
}
return false;
}
async void OnClickTile(Piece? piece, string position)
{
if (SessionName == null) return;
if (SessionName == null || !IsMyTurn) return;
if (selectedPosition == null)
if (selectedPosition == null || piece?.Owner == Perspective)
{
// Select a position.
selectedPosition = position;
return;
}
else if (selectedPosition == position)
if (selectedPosition == position)
{
// Deselect the selected position.
selectedPosition = null;
return;
}
else if (piece != null)
if (piece == null)
{
if (ShouldPromptForPromotion(position) || ShouldPromptForPromotion(selectedPosition))
{
@@ -164,7 +168,7 @@
}
else
{
await ShogiApi.PostMove(SessionName, new MovePieceCommand
await ShogiApi.Move(SessionName, new MovePieceCommand
{
From = selectedPosition,
IsPromotion = false,
@@ -173,6 +177,7 @@
}
}
}
void OnClickHand(Piece piece)
{
selectedPiece = piece.WhichPiece;

View File

@@ -15,6 +15,7 @@
}
.board {
position: relative;
display: grid;
grid-template-areas:
"rank A9 B9 C9 D9 E9 F9 G9 H9 I9"
@@ -59,9 +60,10 @@
overflow: hidden; /* Because SVGs are shaped weird */
transition: filter linear 0.25s;
}
.tile[data-selected] {
.tile[data-selected] {
filter: invert(0.8);
}
}
.ruler {
color: beige;
@@ -91,3 +93,20 @@
display: flex;
flex-wrap: wrap;
}
.promote-prompt {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 2px solid #444;
background-color: #eaeaea;
padding: 1rem;
box-shadow: 1px 1px 1px #444;
text-align: center;
}
.promote-prompt[data-visible="true"] {
display: block;
}

View File

@@ -42,7 +42,7 @@ public class PromotePrompt
if (command != null && sessionName != null)
{
command.IsPromotion = false;
return shogiApi.PostMove(sessionName, command);
return shogiApi.Move(sessionName, command);
}
return Task.CompletedTask;
}
@@ -51,7 +51,7 @@ public class PromotePrompt
if (command != null && sessionName != null)
{
command.IsPromotion = true;
return shogiApi.PostMove(sessionName, command);
return shogiApi.Move(sessionName, command);
}
return Task.CompletedTask;
}

View File

@@ -9,6 +9,6 @@
protected override void OnInitialized()
{
//ModalService.ShowLoginModal();
//Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
}
}

View File

@@ -277,11 +277,11 @@ public class AcceptanceTests : IClassFixture<GuestTestFixture>
// Assert
var session = (await ReadTestSession()).Session;
session.BoardState.Board["A2"].Should().BeNull();
session.BoardState.Board["A3"].Should().NotBeNull();
session.BoardState.Board["A3"]!.IsPromoted.Should().BeFalse();
session.BoardState.Board["A3"]!.Owner.Should().Be(WhichPlayer.Player1);
session.BoardState.Board["A3"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
session.BoardState.Board["A3"].Should().BeNull();
session.BoardState.Board["A4"].Should().NotBeNull();
session.BoardState.Board["A4"]!.IsPromoted.Should().BeFalse();
session.BoardState.Board["A4"]!.Owner.Should().Be(WhichPlayer.Player1);
session.BoardState.Board["A4"]!.WhichPiece.Should().Be(WhichPiece.Pawn);
}
finally
{

View File

@@ -1,92 +1,64 @@
# ASP.NET Core
# Build and test ASP.NET Core projects targeting .NET Core.
# Add steps that run tests, create a NuGet package, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
# ASP.NET
# Build and test ASP.NET projects.
# Add steps that publish symbols, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4
trigger:
- master
pr:
- none
pool:
vmImage: 'windows-latest'
name: 'This is changed in a task, below'
variables:
projectName: 'Gameboard.ShogiUI.Sockets'
version.MajorMinor: '1.0' # Manually change this when needed to follow semver.
version.Patch: $[counter(variables['version.MajorMinor'], 0)]
versionNumber: '$(version.MajorMinor).$(version.Patch)'
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
apiProjectName: 'Shogi.Api'
uiProjectName: 'Shogi.UI'
steps:
# https://github.com/microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md#build-logging-commands
- task: PowerShell@2
displayName: Set the name of the build (i.e. the Build.BuildNumber)
inputs:
targetType: 'inline'
script: |
[string] $buildName = "$(versionNumber)"
Write-Host "Setting the name of the build to '$buildName'."
Write-Host "##vso[build.updatebuildnumber]$buildName"
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
inputs:
command: 'restore'
restoreSolution: '**/*.sln'
feedsToUse: 'config'
- task: DotNetCoreCLI@2
displayName: Publish
env :
DOTNET_CLI_TELEMETRY_OPTOUT: 1
inputs:
command: 'publish'
publishWebProjects: false
projects: '**/*.csproj'
zipAfterPublish: false
arguments: '-c Release'
restoreSolution: '$(solution)'
- task: FileTransform@1
inputs:
folderPath: '$(System.DefaultWorkingDirectory)'
folderPath: '$(System.DefaultWorkingDirectory)\$(uiProjectName)'
fileType: 'json'
targetFiles: '**/appsettings.json'
targetFiles: 'wwwroot/appsettings.json'
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
publishWebProjects: false
arguments: '-c Release'
zipAfterPublish: false
- task: CopyFilesOverSSH@0
displayName: SSH Copy to 1UB
displayName: "Copy API files."
inputs:
sshEndpoint: 'LucaServer'
sourceFolder: '$(System.DefaultWorkingDirectory)\$(projectName)\bin\Release\net6.0\publish'
targetFolder: '/var/www/apps/$(projectName)'
sourceFolder: '$(System.DefaultWorkingDirectory)/$(apiProjectName)/bin/Release/net6.0/publish'
contents: '**'
failOnEmptySource: true
cleanTargetFolder: true
targetFolder: '/var/www/apps/$(apiProjectName)'
readyTimeout: '20000'
- task: CopyFilesOverSSH@0
displayName: "Copy UI files."
inputs:
sshEndpoint: 'LucaServer'
sourceFolder: '$(System.DefaultWorkingDirectory)/$(uiProjectName)/bin/Release/net6.0/publish'
contents: '**'
targetFolder: '/var/www/apps/$(uiProjectName)'
readyTimeout: '20000'
- task: SSH@0
displayName: Restart Kestrel
displayName: "Restart Kestrel"
inputs:
sshEndpoint: 'LucaServer'
runOptions: 'commands'
commands: 'sudo systemctl restart kestrel-gameboard.shogiui.sockets.service'
commands: 'sudo systemctl restart kestrel-shogi.api.service'
readyTimeout: '20000'
- task: DotNetCoreCLI@2
displayName: 'Pack ServiceModels'
inputs:
command: 'pack'
packagesToPack: '**/*ServiceModels*.csproj'
versioningScheme: byBuildNumber
arguments: '-c Release'
- task: DotNetCoreCLI@2
displayName: 'Push NuGet packages to Feed'
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/*.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: '5622b603-b328-44aa-a6e8-8ca56ff54f88/22948853-baa7-4774-b19d-3aed351711c7'