From e3cf2f10595acabca4379d451afa3f3f52f102bc Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Thu, 17 Nov 2022 16:18:15 -0600 Subject: [PATCH 1/2] in the middle of relearning msal --- Shogi.Api/Repositories/SessionRepository.cs | 144 +++++------ Shogi.Api/ShogiUserClaimsTransformer.cs | 2 +- .../Session/Stored Procedures/ReadSession.sql | 2 +- Shogi.UI/App.razor | 8 +- Shogi.UI/Pages/Home/Account/AccountManager.cs | 236 +++++++++--------- .../Home/Account/LocalStorageExtensions.cs | 31 ++- Shogi.UI/Pages/Home/Account/LoginEventArgs.cs | 9 +- Shogi.UI/Pages/Home/Api/IShogiApi.cs | 2 +- Shogi.UI/Pages/Home/Api/ShogiApi.cs | 122 ++++----- Shogi.UI/Pages/Home/GameBoard.razor | 25 +- Shogi.UI/Pages/Home/GameBoard.razor.css | 25 +- Shogi.UI/Pages/Home/PromotePrompt.cs | 4 +- Shogi.UI/Shared/RedirectToLogin.razor | 2 +- Tests/AcceptanceTests/AcceptanceTests.cs | 10 +- 14 files changed, 324 insertions(+), 298 deletions(-) diff --git a/Shogi.Api/Repositories/SessionRepository.cs b/Shogi.Api/Repositories/SessionRepository.cs index 8cadfa1..409290f 100644 --- a/Shogi.Api/Repositories/SessionRepository.cs +++ b/Shogi.Api/Repositories/SessionRepository.cs @@ -9,85 +9,89 @@ namespace Shogi.Api.Repositories; public class SessionRepository : ISessionRepository { - private readonly string connectionString; + private readonly string connectionString; - public SessionRepository(IConfiguration configuration) - { - connectionString = configuration.GetConnectionString("ShogiDatabase"); - } + public SessionRepository(IConfiguration configuration) + { + connectionString = configuration.GetConnectionString("ShogiDatabase"); + } - public async Task CreateSession(Session session) - { - using var connection = new SqlConnection(connectionString); - await connection.ExecuteAsync( - "session.CreateSession", - new - { - session.Name, - Player1Name = session.Player1, - }, - commandType: CommandType.StoredProcedure); - } + public async Task CreateSession(Session session) + { + using var connection = new SqlConnection(connectionString); + await connection.ExecuteAsync( + "session.CreateSession", + new + { + session.Name, + Player1Name = session.Player1, + }, + commandType: CommandType.StoredProcedure); + } - public async Task DeleteSession(string name) - { - using var connection = new SqlConnection(connectionString); - await connection.ExecuteAsync( - "session.DeleteSession", - new { Name = name }, - commandType: CommandType.StoredProcedure); - } + public async Task DeleteSession(string name) + { + using var connection = new SqlConnection(connectionString); + await connection.ExecuteAsync( + "session.DeleteSession", + new { Name = name }, + commandType: CommandType.StoredProcedure); + } - public async Task ReadSession(string name) - { - using var connection = new SqlConnection(connectionString); - var results = await connection.QueryMultipleAsync( - "session.ReadSession", - new { Name = name }, - commandType: CommandType.StoredProcedure); + public async Task ReadSession(string name) + { + using var connection = new SqlConnection(connectionString); + var results = await connection.QueryMultipleAsync( + "session.ReadSession", + new { Name = name }, + commandType: CommandType.StoredProcedure); - var sessionDtos = await results.ReadAsync(); - if (!sessionDtos.Any()) return null; - var dto = sessionDtos.First(); - var session = new Session(dto.Name, dto.Player1); - if (!string.IsNullOrWhiteSpace(dto.Player2)) session.AddPlayer2(dto.Player2); + var sessionDtos = await results.ReadAsync(); + if (!sessionDtos.Any()) return null; + var dto = sessionDtos.First(); + var session = new Session(dto.Name, dto.Player1); + if (!string.IsNullOrWhiteSpace(dto.Player2)) session.AddPlayer2(dto.Player2); - var moveDtos = await results.ReadAsync(); - foreach (var move in moveDtos) - { - if (move.PieceFromHand.HasValue) - { - session.Board.Move(move.PieceFromHand.Value, move.To); - } - else - { - session.Board.Move(move.From, move.To, false); - } - } - return session; - } + var moveDtos = await results.ReadAsync(); + foreach (var move in moveDtos) + { + if (move.PieceFromHand.HasValue) + { + session.Board.Move(move.PieceFromHand.Value, move.To); + } + else if (move.From != null) + { + session.Board.Move(move.From, move.To, false); + } + else + { + throw new InvalidOperationException($"Corrupt data during {nameof(ReadSession)}"); + } + } + return session; + } - public async Task CreateMove(string sessionName, Contracts.Api.MovePieceCommand command) - { - using var connection = new SqlConnection(connectionString); - await connection.ExecuteAsync( - "session.CreateMove", - new - { - command.To, - command.From, - command.IsPromotion, - command.PieceFromHand, - SessionName = sessionName - }, - commandType: CommandType.StoredProcedure); - } + public async Task CreateMove(string sessionName, Contracts.Api.MovePieceCommand command) + { + using var connection = new SqlConnection(connectionString); + await connection.ExecuteAsync( + "session.CreateMove", + new + { + command.To, + command.From, + command.IsPromotion, + command.PieceFromHand, + SessionName = sessionName + }, + commandType: CommandType.StoredProcedure); + } } public interface ISessionRepository { - Task CreateMove(string sessionName, MovePieceCommand command); - Task CreateSession(Session session); - Task DeleteSession(string name); - Task ReadSession(string name); + Task CreateMove(string sessionName, MovePieceCommand command); + Task CreateSession(Session session); + Task DeleteSession(string name); + Task ReadSession(string name); } \ No newline at end of file diff --git a/Shogi.Api/ShogiUserClaimsTransformer.cs b/Shogi.Api/ShogiUserClaimsTransformer.cs index df757e7..c04df1f 100644 --- a/Shogi.Api/ShogiUserClaimsTransformer.cs +++ b/Shogi.Api/ShogiUserClaimsTransformer.cs @@ -47,7 +47,7 @@ public class ShogiUserClaimsTransformer : IShogiUserClaimsTransformer private async Task 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."); diff --git a/Shogi.Database/Session/Stored Procedures/ReadSession.sql b/Shogi.Database/Session/Stored Procedures/ReadSession.sql index 7acdb93..f37abe8 100644 --- a/Shogi.Database/Session/Stored Procedures/ReadSession.sql +++ b/Shogi.Database/Session/Stored Procedures/ReadSession.sql @@ -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 diff --git a/Shogi.UI/App.razor b/Shogi.UI/App.razor index b6c75d4..05d13a9 100644 --- a/Shogi.UI/App.razor +++ b/Shogi.UI/App.razor @@ -1,9 +1,9 @@ -@**@ + - @* + Authorizing!! @@ -17,7 +17,7 @@

You are not authorized to access this resource.

} -
*@ +
@@ -28,4 +28,4 @@
-@*
*@ +
diff --git a/Shogi.UI/Pages/Home/Account/AccountManager.cs b/Shogi.UI/Pages/Home/Account/AccountManager.cs index f77c7d6..156ba35 100644 --- a/Shogi.UI/Pages/Home/Account/AccountManager.cs +++ b/Shogi.UI/Pages/Home/Account/AccountManager.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; using Shogi.UI.Pages.Home.Api; using Shogi.UI.Shared; @@ -6,133 +7,132 @@ namespace Shogi.UI.Pages.Home.Account; public class AccountManager { - private readonly AccountState accountState; - private readonly IShogiApi shogiApi; - private readonly IConfiguration configuration; - private readonly ILocalStorage localStorage; - //private readonly AuthenticationStateProvider authState; - private readonly NavigationManager navigation; - private readonly ShogiSocket shogiSocket; + private readonly AccountState accountState; + private readonly IShogiApi shogiApi; + private readonly IConfiguration configuration; + private readonly ILocalStorage localStorage; + private readonly AuthenticationStateProvider authState; + private readonly NavigationManager navigation; + private readonly ShogiSocket shogiSocket; - public AccountManager( - AccountState accountState, - IShogiApi unauthenticatedClient, - IConfiguration configuration, - //AuthenticationStateProvider authState, - ILocalStorage localStorage, - NavigationManager navigation, - ShogiSocket shogiSocket) - { - this.accountState = accountState; - this.shogiApi = unauthenticatedClient; - this.configuration = configuration; - //this.authState = authState; - this.localStorage = localStorage; - this.navigation = navigation; - this.shogiSocket = shogiSocket; - } + public AccountManager( + AccountState accountState, + IShogiApi unauthenticatedClient, + IConfiguration configuration, + AuthenticationStateProvider authState, + ILocalStorage localStorage, + NavigationManager navigation, + ShogiSocket shogiSocket) + { + this.accountState = accountState; + this.shogiApi = unauthenticatedClient; + this.configuration = configuration; + this.authState = authState; + this.localStorage = localStorage; + this.navigation = navigation; + this.shogiSocket = shogiSocket; + } - private User? User { get => accountState.User; set => accountState.User = value; } + private User? User { get => accountState.User; set => accountState.User = value; } - public async Task LoginWithGuestAccount() - { - var response = await shogiApi.GetToken(); - if (response != null) - { - User = new User - { - DisplayName = response.DisplayName, - Id = response.UserId, - OneTimeSocketToken = response.OneTimeToken, - WhichAccountPlatform = WhichAccountPlatform.Guest - }; - await shogiSocket.OpenAsync(User.OneTimeSocketToken.ToString()); - await localStorage.SetAccountPlatform(WhichAccountPlatform.Guest); - } - } + public async Task LoginWithGuestAccount() + { + var response = await shogiApi.GetToken(); + if (response != null) + { + User = new User + { + DisplayName = response.DisplayName, + Id = response.UserId, + OneTimeSocketToken = response.OneTimeToken, + WhichAccountPlatform = WhichAccountPlatform.Guest + }; + await shogiSocket.OpenAsync(User.OneTimeSocketToken.ToString()); + await localStorage.SetAccountPlatform(WhichAccountPlatform.Guest); + } + } - public async Task LoginWithMicrosoftAccount() - { - throw new NotImplementedException(); - //var state = await authState.GetAuthenticationStateAsync(); + public async Task LoginWithMicrosoftAccount() + { + 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); + } + } - /// - /// Try to log in with the account used from the previous browser session. - /// - /// - public async Task TryLoginSilentAsync() - { - var platform = await localStorage.GetAccountPlatform(); - if (platform == WhichAccountPlatform.Guest) - { - var response = await shogiApi.GetToken(); - if (response != null) - { - User = new User - { - DisplayName = response.DisplayName, - Id = response.UserId, - OneTimeSocketToken = response.OneTimeToken, - WhichAccountPlatform = WhichAccountPlatform.Guest - }; - } - } - else if (platform == WhichAccountPlatform.Microsoft) - { - Console.WriteLine("Login Microsoft"); - throw new NotImplementedException(); - //var state = await authState.GetAuthenticationStateAsync(); - //if (state.User?.Identity?.Name != null) - //{ - // var id = state.User.Identity; - // User = new User - // { - // DisplayName = id.Name, - // Id = id.Name - // }; - // var token = await shogiApi.GetToken(); - // if (token.HasValue) - // { - // User.OneTimeSocketToken = token.Value; - // } - //} - // TODO: If this fails then platform saved to localStorage should get cleared - } + /// + /// Try to log in with the account used from the previous browser session. + /// + /// + public async Task TryLoginSilentAsync() + { + var platform = await localStorage.GetAccountPlatform(); + if (platform == WhichAccountPlatform.Guest) + { + var response = await shogiApi.GetToken(); + if (response != null) + { + User = new User + { + DisplayName = response.DisplayName, + Id = response.UserId, + OneTimeSocketToken = response.OneTimeToken, + WhichAccountPlatform = WhichAccountPlatform.Guest + }; + } + } + else if (platform == WhichAccountPlatform.Microsoft) + { + Console.WriteLine("Login Microsoft"); + throw new NotImplementedException(); + //var state = await authState.GetAuthenticationStateAsync(); + //if (state.User?.Identity?.Name != null) + //{ + // var id = state.User.Identity; + // User = new User + // { + // DisplayName = id.Name, + // Id = id.Name + // }; + // var token = await shogiApi.GetToken(); + // if (token.HasValue) + // { + // User.OneTimeSocketToken = token.Value; + // } + //} + // TODO: If this fails then platform saved to localStorage should get cleared + } - if (User != null) - { - await shogiSocket.OpenAsync(User.OneTimeSocketToken.ToString()); - return true; - } + if (User != null) + { + await shogiSocket.OpenAsync(User.OneTimeSocketToken.ToString()); + return true; + } - return false; - } + return false; + } - public async Task LogoutAsync() - { - await Task.WhenAll(shogiApi.GuestLogout(), localStorage.DeleteAccountPlatform()); - User = null; - } + public async Task LogoutAsync() + { + await Task.WhenAll(shogiApi.GuestLogout(), localStorage.DeleteAccountPlatform()); + User = null; + } } diff --git a/Shogi.UI/Pages/Home/Account/LocalStorageExtensions.cs b/Shogi.UI/Pages/Home/Account/LocalStorageExtensions.cs index fe76196..9d79c31 100644 --- a/Shogi.UI/Pages/Home/Account/LocalStorageExtensions.cs +++ b/Shogi.UI/Pages/Home/Account/LocalStorageExtensions.cs @@ -1,24 +1,23 @@ 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 GetAccountPlatform(this ILocalStorage self) { - private const string AccountPlatform = "AccountPlatform"; + return self.Get(AccountPlatform).AsTask(); + } - public static Task GetAccountPlatform(this ILocalStorage self) - { - return self.Get(AccountPlatform).AsTask(); - } + public static Task SetAccountPlatform(this ILocalStorage self, WhichAccountPlatform platform) + { + return self.Set(AccountPlatform, platform.ToString()).AsTask(); + } - public static Task SetAccountPlatform(this ILocalStorage self, WhichAccountPlatform platform) - { - return self.Set(AccountPlatform, platform.ToString()).AsTask(); - } - - public static Task DeleteAccountPlatform(this ILocalStorage self) - { - return self.Delete(AccountPlatform).AsTask(); - } + public static Task DeleteAccountPlatform(this ILocalStorage self) + { + return self.Delete(AccountPlatform).AsTask(); } } diff --git a/Shogi.UI/Pages/Home/Account/LoginEventArgs.cs b/Shogi.UI/Pages/Home/Account/LoginEventArgs.cs index 865e387..f47db01 100644 --- a/Shogi.UI/Pages/Home/Account/LoginEventArgs.cs +++ b/Shogi.UI/Pages/Home/Account/LoginEventArgs.cs @@ -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; } - } + public User? User { get; set; } } diff --git a/Shogi.UI/Pages/Home/Api/IShogiApi.cs b/Shogi.UI/Pages/Home/Api/IShogiApi.cs index a77ee7f..d726a62 100644 --- a/Shogi.UI/Pages/Home/Api/IShogiApi.cs +++ b/Shogi.UI/Pages/Home/Api/IShogiApi.cs @@ -10,6 +10,6 @@ public interface IShogiApi Task GetSessionsPlayerCount(); Task GetToken(); Task GuestLogout(); - Task PostMove(string sessionName, MovePieceCommand move); + Task Move(string sessionName, MovePieceCommand move); Task PostSession(string name, bool isPrivate); } \ No newline at end of file diff --git a/Shogi.UI/Pages/Home/Api/ShogiApi.cs b/Shogi.UI/Pages/Home/Api/ShogiApi.cs index ab1accc..5f025c2 100644 --- a/Shogi.UI/Pages/Home/Api/ShogiApi.cs +++ b/Shogi.UI/Pages/Home/Api/ShogiApi.cs @@ -7,74 +7,74 @@ using System.Text.Json; namespace Shogi.UI.Pages.Home.Api { - public class ShogiApi : IShogiApi - { - public const string GuestClientName = "Guest"; - public const string MsalClientName = "Msal"; - public const string AnonymouseClientName = "Anonymous"; + public class ShogiApi : IShogiApi + { + public const string GuestClientName = "Guest"; + public const string MsalClientName = "Msal"; + public const string AnonymouseClientName = "Anonymous"; - private readonly JsonSerializerOptions serializerOptions; - private readonly IHttpClientFactory clientFactory; - private readonly AccountState accountState; + private readonly JsonSerializerOptions serializerOptions; + private readonly IHttpClientFactory clientFactory; + private readonly AccountState accountState; - public ShogiApi(IHttpClientFactory clientFactory, AccountState accountState) - { - serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); - this.clientFactory = clientFactory; - this.accountState = accountState; - } + public ShogiApi(IHttpClientFactory clientFactory, AccountState accountState) + { + serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); + this.clientFactory = clientFactory; + this.accountState = accountState; + } - private HttpClient HttpClient => accountState.User?.WhichAccountPlatform switch - { - WhichAccountPlatform.Guest => clientFactory.CreateClient(GuestClientName), - WhichAccountPlatform.Microsoft => clientFactory.CreateClient(MsalClientName), - _ => clientFactory.CreateClient(AnonymouseClientName) - }; + private HttpClient HttpClient => accountState.User?.WhichAccountPlatform switch + { + WhichAccountPlatform.Guest => clientFactory.CreateClient(GuestClientName), + WhichAccountPlatform.Microsoft => clientFactory.CreateClient(MsalClientName), + _ => clientFactory.CreateClient(AnonymouseClientName) + }; - public async Task GuestLogout() - { - var response = await HttpClient.PutAsync(new Uri("User/GuestLogout", UriKind.Relative), null); - response.EnsureSuccessStatusCode(); - } + public async Task GuestLogout() + { + var response = await HttpClient.PutAsync(new Uri("User/GuestLogout", UriKind.Relative), null); + response.EnsureSuccessStatusCode(); + } - public async Task GetSession(string name) - { - var response = await HttpClient.GetAsync(new Uri($"Sessions/{name}", UriKind.Relative)); - if (response.IsSuccessStatusCode) - { - return (await response.Content.ReadFromJsonAsync(serializerOptions))?.Session; - } - return null; - } + public async Task GetSession(string name) + { + var response = await HttpClient.GetAsync(new Uri($"Sessions/{name}", UriKind.Relative)); + if (response.IsSuccessStatusCode) + { + return (await response.Content.ReadFromJsonAsync(serializerOptions))?.Session; + } + return null; + } - public async Task GetSessionsPlayerCount() - { - var response = await HttpClient.GetAsync(new Uri("Sessions/PlayerCount", UriKind.Relative)); - if (response.IsSuccessStatusCode) - { - return await response.Content.ReadFromJsonAsync(serializerOptions); - } - return null; - } + public async Task GetSessionsPlayerCount() + { + var response = await HttpClient.GetAsync(new Uri("Sessions/PlayerCount", UriKind.Relative)); + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadFromJsonAsync(serializerOptions); + } + return null; + } - public async Task GetToken() - { - var response = await HttpClient.GetFromJsonAsync(new Uri("User/Token", UriKind.Relative), serializerOptions); - return response; - } + public async Task GetToken() + { + var response = await HttpClient.GetFromJsonAsync(new Uri("User/Token", UriKind.Relative), serializerOptions); + return response; + } - public async Task PostMove(string sessionName, MovePieceCommand command) - { - await this.HttpClient.PostAsJsonAsync($"Sessions{sessionName}/Move", command); - } + public async Task Move(string sessionName, MovePieceCommand command) + { + await this.HttpClient.PatchAsync($"Sessions/{sessionName}/Move", JsonContent.Create(command)); + } - public async Task PostSession(string name, bool isPrivate) - { - var response = await HttpClient.PostAsJsonAsync(new Uri("Sessions", UriKind.Relative), new CreateSessionCommand - { - Name = name, - }); - return response.StatusCode; - } - } + public async Task PostSession(string name, bool isPrivate) + { + var response = await HttpClient.PostAsJsonAsync(new Uri("Sessions", UriKind.Relative), new CreateSessionCommand + { + Name = name, + }); + return response.StatusCode; + } + } } diff --git a/Shogi.UI/Pages/Home/GameBoard.razor b/Shogi.UI/Pages/Home/GameBoard.razor index 3fc8200..adb5552 100644 --- a/Shogi.UI/Pages/Home/GameBoard.razor +++ b/Shogi.UI/Pages/Home/GameBoard.razor @@ -15,10 +15,10 @@ var position = $"{file}{rank}"; var piece = session?.BoardState.Board[position];
+ data-position="@(position)" + data-selected="@(piece != null && selectedPosition == position)" + style="grid-area: @(position)" + @onclick="() => OnClickTile(piece, position)">
} @@ -46,7 +46,7 @@ I -
+

Do you wish to promote?

@@ -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; diff --git a/Shogi.UI/Pages/Home/GameBoard.razor.css b/Shogi.UI/Pages/Home/GameBoard.razor.css index b3e7197..d59fdc1 100644 --- a/Shogi.UI/Pages/Home/GameBoard.razor.css +++ b/Shogi.UI/Pages/Home/GameBoard.razor.css @@ -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] { - filter: invert(0.8); -} + + .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; + } diff --git a/Shogi.UI/Pages/Home/PromotePrompt.cs b/Shogi.UI/Pages/Home/PromotePrompt.cs index dbd1cf8..116c8d9 100644 --- a/Shogi.UI/Pages/Home/PromotePrompt.cs +++ b/Shogi.UI/Pages/Home/PromotePrompt.cs @@ -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; } diff --git a/Shogi.UI/Shared/RedirectToLogin.razor b/Shogi.UI/Shared/RedirectToLogin.razor index d7864b2..8a7fa95 100644 --- a/Shogi.UI/Shared/RedirectToLogin.razor +++ b/Shogi.UI/Shared/RedirectToLogin.razor @@ -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)}"); } } diff --git a/Tests/AcceptanceTests/AcceptanceTests.cs b/Tests/AcceptanceTests/AcceptanceTests.cs index b62c53d..842e6bd 100644 --- a/Tests/AcceptanceTests/AcceptanceTests.cs +++ b/Tests/AcceptanceTests/AcceptanceTests.cs @@ -277,11 +277,11 @@ public class AcceptanceTests : IClassFixture // 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 { From 66340c0dee798fb3efb3e70f2838f70494633991 Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Sat, 14 Jan 2023 16:51:38 +0000 Subject: [PATCH 2/2] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 98 ++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 63 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1355b2e..1a323e7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -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' - 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' \ No newline at end of file + commands: 'sudo systemctl restart kestrel-shogi.api.service' + readyTimeout: '20000' \ No newline at end of file