From 2d0687a0830ec304c249a1cb77fe71906c792571 Mon Sep 17 00:00:00 2001 From: Tim Krampitz Date: Sat, 3 Oct 2026 22:29:33 +0200 Subject: [PATCH 1/6] Lobby: include HostUserId in GameStateDto and let client derive host status; update tests --- SlipItIn.Server.Tests/GameHubTests.cs | 9 ++++++--- SlipItIn.Server.Tests/GameServiceTests.cs | 12 ++++++++++++ SlipItIn.Server/Services/GameService.cs | 1 + SlipItIn.Shared/DTOs/GameStateDto.cs | 2 ++ SlipItIn/ViewModels/LobbyViewModel.cs | 6 ++++++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/SlipItIn.Server.Tests/GameHubTests.cs b/SlipItIn.Server.Tests/GameHubTests.cs index 0b9ab7b..669a609 100644 --- a/SlipItIn.Server.Tests/GameHubTests.cs +++ b/SlipItIn.Server.Tests/GameHubTests.cs @@ -108,6 +108,7 @@ public class GameHubTests { GameId = game.Id, LobbyCode = game.LobbyCode, + HostUserId = game.HostId, Players = [new PlayerInfoDto { Username = user.Username }] }); @@ -151,7 +152,7 @@ public class GameHubTests var game = new Game { Id = 3, LobbyCode = "JOIN42", HostId = 99 }; _gameService.Setup(s => s.JoinGameAsync("JOIN42", user.Id, ConnectionId)).ReturnsAsync(game); - _gameService.Setup(s => s.GetGameStateAsync(game.Id)).ReturnsAsync(new GameStateDto { GameId = 3 }); + _gameService.Setup(s => s.GetGameStateAsync(game.Id)).ReturnsAsync(new GameStateDto { GameId = 3, HostUserId = 99 }); await _hub.JoinLobby("JOIN42"); @@ -205,7 +206,7 @@ public class GameHubTests _gameService.Setup(s => s.SetPlayerReadyAsync(game.Id, player.Id)) .ReturnsAsync(new Game { Id = game.Id, LobbyCode = game.LobbyCode }); - _gameService.Setup(s => s.GetGameStateAsync(game.Id)).ReturnsAsync(new GameStateDto { GameId = game.Id }); + _gameService.Setup(s => s.GetGameStateAsync(game.Id)).ReturnsAsync(new GameStateDto { GameId = game.Id, HostUserId = game.HostId }); await _hub.PlayerReady(game.Id, player.Id); @@ -540,7 +541,9 @@ public class GameHubTests SeedGameWithPlayers((user, ConnectionId)); var gameId = db.Games.Single().Id; - _gameService.Setup(s => s.GetGameStateAsync(gameId)).ReturnsAsync(new GameStateDto { GameId = gameId }); + var hostId = db.Games.Single().HostId; + + _gameService.Setup(s => s.GetGameStateAsync(gameId)).ReturnsAsync(new GameStateDto { GameId = gameId, HostUserId = hostId }); await _hub.RequestGameState(gameId); diff --git a/SlipItIn.Server.Tests/GameServiceTests.cs b/SlipItIn.Server.Tests/GameServiceTests.cs index a97b32d..f9fab2a 100644 --- a/SlipItIn.Server.Tests/GameServiceTests.cs +++ b/SlipItIn.Server.Tests/GameServiceTests.cs @@ -40,6 +40,18 @@ public class GameServiceTests await Assert.ThrowsAsync(() => _sut.CreateGameAsync(999)); } + [Fact] + public async Task GetGameStateAsync_IncludesHostUserId() + { + using var db = _factory.CreateDbContext(); + var host = TestDbHelper.SeedUser(db); + var (game, _) = TestDbHelper.SeedGame(db, host); + + var dto = await _sut.GetGameStateAsync(game.Id); + + Assert.Equal(game.HostId, dto.HostUserId); + } + // ---------- JoinGameAsync ---------- [Fact] diff --git a/SlipItIn.Server/Services/GameService.cs b/SlipItIn.Server/Services/GameService.cs index b57f4ff..50ee4a8 100644 --- a/SlipItIn.Server/Services/GameService.cs +++ b/SlipItIn.Server/Services/GameService.cs @@ -293,6 +293,7 @@ public class GameService : IGameService LobbyCode = game.LobbyCode, Status = game.Status.ToString(), Players = playerInfos, + HostUserId = game.HostId, CurrentRound = currentRound?.RoundNumber ?? 0, RoundTimeRemaining = roundTimeRemaining }; diff --git a/SlipItIn.Shared/DTOs/GameStateDto.cs b/SlipItIn.Shared/DTOs/GameStateDto.cs index 8bbccbb..7ca3424 100644 --- a/SlipItIn.Shared/DTOs/GameStateDto.cs +++ b/SlipItIn.Shared/DTOs/GameStateDto.cs @@ -6,6 +6,8 @@ public class GameStateDto public string LobbyCode { get; set; } = string.Empty; public string Status { get; set; } = string.Empty; public List Players { get; set; } = []; + // Host user id to let clients determine whether the current user is the host + public int HostUserId { get; set; } public int CurrentRound { get; set; } public int RoundTimeRemaining { get; set; } } diff --git a/SlipItIn/ViewModels/LobbyViewModel.cs b/SlipItIn/ViewModels/LobbyViewModel.cs index 87184f1..cb62a8d 100644 --- a/SlipItIn/ViewModels/LobbyViewModel.cs +++ b/SlipItIn/ViewModels/LobbyViewModel.cs @@ -144,8 +144,14 @@ public partial class LobbyViewModel : BaseViewModel, Players.Add(player); var currentUsername = _authSession.CurrentUser?.Username; + var currentUserId = _authSession.CurrentUser?.UserId ?? 0; + IsCurrentPlayerReady = message.Value.Players.Any(p => p.IsReady && string.Equals(p.Username, currentUsername, StringComparison.OrdinalIgnoreCase)); + + // Determine whether the current user is the host based on server-provided HostUserId + IsHost = currentUserId > 0 && message.Value.HostUserId > 0 && currentUserId == message.Value.HostUserId; + CanStartGame = message.Value.Players.Count >= 2 && message.Value.Players.All(p => p.IsReady); }); } -- 2.54.0 From 8b5dd6f45d3ddbebf290322b7b00f7e9c76286a9 Mon Sep 17 00:00:00 2001 From: Tim Krampitz Date: Sun, 4 Oct 2026 17:53:54 +0200 Subject: [PATCH 2/6] CI: restore and build MAUI project with win-x64 runtime identifier to ensure runtime packs are restored --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22c489a..f3df7f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,8 @@ jobs: run: dotnet workload install maui - name: Restore - run: dotnet restore SlipItIn.slnx + # Restore with a runtime identifier so the Windows MAUI runtime packs are downloaded + run: dotnet restore SlipItIn.slnx --runtime win-x64 - name: Build Server-Stack run: | @@ -34,7 +35,8 @@ jobs: dotnet build SlipItIn.AppHost/SlipItIn.AppHost.csproj --no-restore --configuration Release - name: Build MAUI Client - run: dotnet build SlipItIn/SlipItIn.csproj --no-restore --configuration Debug + # Build the MAUI project for the Windows runtime identifier so the proper runtime packs are used + run: dotnet build SlipItIn/SlipItIn.csproj --no-restore --configuration Debug -r win-x64 - name: Run Server Tests run: dotnet test SlipItIn.Server.Tests/SlipItIn.Server.Tests.csproj --no-restore --configuration Release -- 2.54.0 From c4915542fe0a58d0f7d4d006be6ba1c1aa7abd18 Mon Sep 17 00:00:00 2001 From: Tim Krampitz Date: Sun, 4 Oct 2026 17:56:55 +0200 Subject: [PATCH 3/6] CI: build MAUI target for Android in CI and restore without Windows runtime to avoid missing Mono runtime pack when running locally with act --- .github/workflows/build.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3df7f0..2b53048 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,8 +23,7 @@ jobs: run: dotnet workload install maui - name: Restore - # Restore with a runtime identifier so the Windows MAUI runtime packs are downloaded - run: dotnet restore SlipItIn.slnx --runtime win-x64 + run: dotnet restore SlipItIn.slnx - name: Build Server-Stack run: | @@ -35,8 +34,8 @@ jobs: dotnet build SlipItIn.AppHost/SlipItIn.AppHost.csproj --no-restore --configuration Release - name: Build MAUI Client - # Build the MAUI project for the Windows runtime identifier so the proper runtime packs are used - run: dotnet build SlipItIn/SlipItIn.csproj --no-restore --configuration Debug -r win-x64 + # Build the MAUI project for Android only in CI to avoid requiring Windows runtime packs + run: dotnet build SlipItIn/SlipItIn.csproj --no-restore --configuration Debug -f net10.0-android - name: Run Server Tests run: dotnet test SlipItIn.Server.Tests/SlipItIn.Server.Tests.csproj --no-restore --configuration Release -- 2.54.0 From 21cb57fd74362b88e94ed288b2ba8eef72d32309 Mon Sep 17 00:00:00 2001 From: Tim Krampitz Date: Sun, 4 Oct 2026 17:58:18 +0200 Subject: [PATCH 4/6] CI: avoid restoring Windows MAUI runtime packs in CI/tooling by skipping Windows TFM; make SkipWindowsTargetFramework property controllable --- .github/workflows/build.yml | 3 ++- SlipItIn/SlipItIn.csproj | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b53048..bb4c193 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,8 @@ jobs: run: dotnet workload install maui - name: Restore - run: dotnet restore SlipItIn.slnx + # Skip restoring the Windows target frameworks to avoid requiring Windows runtime packs in CI/tooling environments + run: dotnet restore SlipItIn.slnx -p:SkipWindowsTargetFramework=true - name: Build Server-Stack run: | diff --git a/SlipItIn/SlipItIn.csproj b/SlipItIn/SlipItIn.csproj index 8d2acfa..49c954d 100644 --- a/SlipItIn/SlipItIn.csproj +++ b/SlipItIn/SlipItIn.csproj @@ -3,7 +3,8 @@ net10.0-android $(TargetFrameworks); - $(TargetFrameworks);net10.0-windows10.0.19041.0 + + $(TargetFrameworks);net10.0-windows10.0.19041.0 preview - $(TargetFrameworks);net10.0-windows10.0.19041.0 + + $(TargetFrameworks);net10.0-windows10.0.19041.0 preview