From 1914de2bcf7030f54317f3496a7cc7fdea75d5fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 05:26:27 +0000 Subject: [PATCH 1/2] Initial plan From a1b79a62323e2f4e8bf0b9e6ecc3406a625e342b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 05:49:52 +0000 Subject: [PATCH 2/2] Add Azure Login management and rename Admin Index to Settings Co-authored-by: qkfang <138417504+qkfang@users.noreply.github.com> --- .../Controllers/AdminController.cs | 24 +- .../Controllers/AzureLoginController.cs | 44 ++++ apps-rps/rps-game-server/Models/LoginEntry.cs | 15 ++ apps-rps/rps-game-server/Program.cs | 1 + .../rps-game-server/Services/ILoginService.cs | 9 + .../rps-game-server/Services/LoginService.cs | 88 ++++++++ .../rps-game-server/Views/Admin/Index.cshtml | 206 +----------------- .../Views/Admin/Settings.cshtml | 203 +++++++++++++++++ .../Views/AzureLogin/Index.cshtml | 162 ++++++++++++++ .../Views/Shared/_Layout.cshtml | 5 +- apps-rps/rps-game-server/login.json | 27 +++ 11 files changed, 577 insertions(+), 207 deletions(-) create mode 100644 apps-rps/rps-game-server/Controllers/AzureLoginController.cs create mode 100644 apps-rps/rps-game-server/Models/LoginEntry.cs create mode 100644 apps-rps/rps-game-server/Services/ILoginService.cs create mode 100644 apps-rps/rps-game-server/Services/LoginService.cs create mode 100644 apps-rps/rps-game-server/Views/Admin/Settings.cshtml create mode 100644 apps-rps/rps-game-server/Views/AzureLogin/Index.cshtml create mode 100644 apps-rps/rps-game-server/login.json diff --git a/apps-rps/rps-game-server/Controllers/AdminController.cs b/apps-rps/rps-game-server/Controllers/AdminController.cs index 47f0b93..e3d31ec 100644 --- a/apps-rps/rps-game-server/Controllers/AdminController.cs +++ b/apps-rps/rps-game-server/Controllers/AdminController.cs @@ -21,10 +21,10 @@ public AdminController(ITournamentService tournamentService, IQuestionService qu public IActionResult Login() { - // If already authenticated, redirect to admin index + // If already authenticated, redirect to settings if (IsAuthenticated()) { - return RedirectToAction("Index"); + return RedirectToAction("Settings"); } return View(); @@ -36,7 +36,7 @@ public IActionResult Login(string passcode) if (passcode == AdminPasscode) { HttpContext.Session.SetString(AdminSessionKey, "true"); - return RedirectToAction("Index"); + return RedirectToAction("Settings"); } TempData["Error"] = "Invalid passcode. Please try again."; @@ -50,6 +50,16 @@ public IActionResult Index() return RedirectToAction("Login"); } + return RedirectToAction("Settings"); + } + + public IActionResult Settings() + { + if (!IsAuthenticated()) + { + return RedirectToAction("Login"); + } + var tournament = _tournamentService.GetTournament(); return View(tournament); } @@ -72,7 +82,7 @@ public IActionResult UnregisterPlayer(int playerId) TempData["Error"] = "Failed to unregister player."; } - return RedirectToAction("Index"); + return RedirectToAction("Settings"); } [HttpPost] @@ -93,7 +103,7 @@ public IActionResult UnregisterAllPlayers() TempData["Error"] = "No players to unregister or operation failed."; } - return RedirectToAction("Index"); + return RedirectToAction("Settings"); } [HttpPost] @@ -114,7 +124,7 @@ public IActionResult ResetCurrentRound() TempData["Error"] = "Failed to reset current round."; } - return RedirectToAction("Index"); + return RedirectToAction("Settings"); } [HttpPost] @@ -135,7 +145,7 @@ public IActionResult ResetTournament() TempData["Error"] = "Failed to reset tournament."; } - return RedirectToAction("Index"); + return RedirectToAction("Settings"); } [HttpPost] diff --git a/apps-rps/rps-game-server/Controllers/AzureLoginController.cs b/apps-rps/rps-game-server/Controllers/AzureLoginController.cs new file mode 100644 index 0000000..0e104ab --- /dev/null +++ b/apps-rps/rps-game-server/Controllers/AzureLoginController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using RpsGameServer.Services; + +namespace RpsGameServer.Controllers; + +public class AzureLoginController : Controller +{ + private readonly ILoginService _loginService; + + public AzureLoginController(ILoginService loginService) + { + _loginService = loginService; + } + + public async Task Index() + { + var logins = await _loginService.GetAllLoginsAsync(); + return View(logins); + } + + [HttpPost] + public async Task ClaimLogin(string claimedBy) + { + if (string.IsNullOrWhiteSpace(claimedBy)) + { + TempData["Error"] = "Please enter your name to claim a login."; + return RedirectToAction("Index"); + } + + var login = await _loginService.ClaimLoginAsync(claimedBy); + if (login == null) + { + TempData["Error"] = "No available logins to claim. All entries have been claimed."; + return RedirectToAction("Index"); + } + + TempData["Success"] = $"Login claimed successfully!"; + TempData["LoginEmail"] = login.LoginEmail; + TempData["LoginPassword"] = login.LoginPassword; + TempData["ClaimedBy"] = login.ClaimedBy; + + return RedirectToAction("Index"); + } +} diff --git a/apps-rps/rps-game-server/Models/LoginEntry.cs b/apps-rps/rps-game-server/Models/LoginEntry.cs new file mode 100644 index 0000000..7b63d73 --- /dev/null +++ b/apps-rps/rps-game-server/Models/LoginEntry.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace RpsGameServer.Models; + +public class LoginEntry +{ + [JsonPropertyName("loginEmail")] + public string LoginEmail { get; set; } = string.Empty; + + [JsonPropertyName("loginPassword")] + public string LoginPassword { get; set; } = string.Empty; + + [JsonPropertyName("claimedBy")] + public string? ClaimedBy { get; set; } +} diff --git a/apps-rps/rps-game-server/Program.cs b/apps-rps/rps-game-server/Program.cs index aa5fd3b..0b5e481 100644 --- a/apps-rps/rps-game-server/Program.cs +++ b/apps-rps/rps-game-server/Program.cs @@ -37,6 +37,7 @@ builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); var app = builder.Build(); diff --git a/apps-rps/rps-game-server/Services/ILoginService.cs b/apps-rps/rps-game-server/Services/ILoginService.cs new file mode 100644 index 0000000..16fe63e --- /dev/null +++ b/apps-rps/rps-game-server/Services/ILoginService.cs @@ -0,0 +1,9 @@ +using RpsGameServer.Models; + +namespace RpsGameServer.Services; + +public interface ILoginService +{ + Task> GetAllLoginsAsync(); + Task ClaimLoginAsync(string claimedBy); +} diff --git a/apps-rps/rps-game-server/Services/LoginService.cs b/apps-rps/rps-game-server/Services/LoginService.cs new file mode 100644 index 0000000..c33b82c --- /dev/null +++ b/apps-rps/rps-game-server/Services/LoginService.cs @@ -0,0 +1,88 @@ +using System.Text.Json; +using RpsGameServer.Models; + +namespace RpsGameServer.Services; + +public class LoginService : ILoginService +{ + private readonly string _loginFilePath; + private readonly SemaphoreSlim _lock = new(1, 1); + + public LoginService(IWebHostEnvironment environment) + { + _loginFilePath = Path.Combine(environment.ContentRootPath, "login.json"); + } + + public async Task> GetAllLoginsAsync() + { + await _lock.WaitAsync(); + try + { + if (!File.Exists(_loginFilePath)) + { + return new List(); + } + + var json = await File.ReadAllTextAsync(_loginFilePath); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + var logins = JsonSerializer.Deserialize>(json, options) ?? new List(); + return logins; + } + finally + { + _lock.Release(); + } + } + + public async Task ClaimLoginAsync(string claimedBy) + { + await _lock.WaitAsync(); + try + { + var logins = await GetAllLoginsWithoutLockAsync(); + var availableLogin = logins.FirstOrDefault(l => string.IsNullOrEmpty(l.ClaimedBy)); + + if (availableLogin == null) + { + return null; + } + + availableLogin.ClaimedBy = claimedBy; + await SaveLoginsAsync(logins); + return availableLogin; + } + finally + { + _lock.Release(); + } + } + + private async Task> GetAllLoginsWithoutLockAsync() + { + if (!File.Exists(_loginFilePath)) + { + return new List(); + } + + var json = await File.ReadAllTextAsync(_loginFilePath); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + var logins = JsonSerializer.Deserialize>(json, options) ?? new List(); + return logins; + } + + private async Task SaveLoginsAsync(List logins) + { + var options = new JsonSerializerOptions + { + WriteIndented = true + }; + var json = JsonSerializer.Serialize(logins, options); + await File.WriteAllTextAsync(_loginFilePath, json); + } +} diff --git a/apps-rps/rps-game-server/Views/Admin/Index.cshtml b/apps-rps/rps-game-server/Views/Admin/Index.cshtml index d935c14..81c9327 100644 --- a/apps-rps/rps-game-server/Views/Admin/Index.cshtml +++ b/apps-rps/rps-game-server/Views/Admin/Index.cshtml @@ -3,201 +3,11 @@ ViewData["Title"] = "Admin Panel"; } -
-
-
-
-
-

- Admin Panel -

-
- -
-
-
-
- @if (TempData["Success"] != null) - { -
- @TempData["Success"] -
- } - @if (TempData["Error"] != null) - { -
- @TempData["Error"] -
- } - - -
-
-

Tournament Status

-
- Status: @Model.Status
- Current Round: @Model.CurrentRound / @RpsGameServer.Models.Tournament.MaxRounds
- Total Players: @Model.Players.Count -
-
-
- - -
-
-
-
-
Round Management
-
-
-

Reset the current round, clearing all submissions and scores for this round only.

-
- -
-
-
-
- -
-
-
-
Tournament Reset
-
-
-

Reset the entire tournament while keeping all registered players.

-
- -
-
-
-
- -
-
-
-
Player Management
-
-
-

Manage registered players by removing them from the tournament.

- - Manage Players - -
-
-
-
- - -
-
-
-
-
Questions Configuration
-
-
-

View and configure questions for the tournament. Supports text, audio, image, and video questions.

- - Manage Questions - -
-
-
- -
-
-
-
Tournament History
-
-
-

View past tournament results, player performance, and detailed round information.

- - View History - -
-
-
-
- - -
-
-
-

Registered Players

- @if (Model.Players.Any()) - { -
- -
- } -
- @if (Model.Players.Any()) - { -
- - - - - - - - - - - - @foreach (var player in Model.Players.OrderBy(p => p.Id)) - { - - - - - - - - } - -
IDNameRegistered AtTotal ScoreAction
@player.Id@player.Name@player.RegisteredAt.ToString("yyyy-MM-dd HH:mm")@player.TotalScore -
- - -
-
-
- } - else - { -
- No players are currently registered. -
- } -
-
- - - -
-
-
-
\ No newline at end of file +
+

Admin Panel

+

Redirecting to Settings...

+
+ + \ No newline at end of file diff --git a/apps-rps/rps-game-server/Views/Admin/Settings.cshtml b/apps-rps/rps-game-server/Views/Admin/Settings.cshtml new file mode 100644 index 0000000..d935c14 --- /dev/null +++ b/apps-rps/rps-game-server/Views/Admin/Settings.cshtml @@ -0,0 +1,203 @@ +@model RpsGameServer.Models.Tournament +@{ + ViewData["Title"] = "Admin Panel"; +} + +
+
+
+
+
+

+ Admin Panel +

+
+ +
+
+
+
+ @if (TempData["Success"] != null) + { +
+ @TempData["Success"] +
+ } + @if (TempData["Error"] != null) + { +
+ @TempData["Error"] +
+ } + + +
+
+

Tournament Status

+
+ Status: @Model.Status
+ Current Round: @Model.CurrentRound / @RpsGameServer.Models.Tournament.MaxRounds
+ Total Players: @Model.Players.Count +
+
+
+ + +
+
+
+
+
Round Management
+
+
+

Reset the current round, clearing all submissions and scores for this round only.

+
+ +
+
+
+
+ +
+
+
+
Tournament Reset
+
+
+

Reset the entire tournament while keeping all registered players.

+
+ +
+
+
+
+ +
+
+
+
Player Management
+
+
+

Manage registered players by removing them from the tournament.

+ + Manage Players + +
+
+
+
+ + +
+
+
+
+
Questions Configuration
+
+
+

View and configure questions for the tournament. Supports text, audio, image, and video questions.

+ + Manage Questions + +
+
+
+ +
+
+
+
Tournament History
+
+
+

View past tournament results, player performance, and detailed round information.

+ + View History + +
+
+
+
+ + +
+
+
+

Registered Players

+ @if (Model.Players.Any()) + { +
+ +
+ } +
+ @if (Model.Players.Any()) + { +
+ + + + + + + + + + + + @foreach (var player in Model.Players.OrderBy(p => p.Id)) + { + + + + + + + + } + +
IDNameRegistered AtTotal ScoreAction
@player.Id@player.Name@player.RegisteredAt.ToString("yyyy-MM-dd HH:mm")@player.TotalScore +
+ + +
+
+
+ } + else + { +
+ No players are currently registered. +
+ } +
+
+ + + +
+
+
+
\ No newline at end of file diff --git a/apps-rps/rps-game-server/Views/AzureLogin/Index.cshtml b/apps-rps/rps-game-server/Views/AzureLogin/Index.cshtml new file mode 100644 index 0000000..9ac0a15 --- /dev/null +++ b/apps-rps/rps-game-server/Views/AzureLogin/Index.cshtml @@ -0,0 +1,162 @@ +@model List +@{ + ViewData["Title"] = "Azure Login Management"; +} + +
+
+
+
+

+ Azure Login Management +

+
+
+ @if (TempData["Success"] != null) + { +
+ @TempData["Success"] + @if (TempData["LoginEmail"] != null) + { +
+
+
Your Login Credentials:
+
+

Email: @TempData["LoginEmail"]

+

Password: @TempData["LoginPassword"]

+

Claimed By: @TempData["ClaimedBy"]

+
+
+ Important: Please save these credentials. They will not be shown again. +
+
+ } +
+ } + @if (TempData["Error"] != null) + { +
+ @TempData["Error"] +
+ } + + +
+
+
Claim a Login
+
+
+
+
+ + + This name will be associated with the login you claim. +
+ +
+
+
+ + +
+
+
All Login Entries
+
+
+ @if (Model != null && Model.Any()) + { +
+ + + + + + + + + + + + @for (int i = 0; i < Model.Count; i++) + { + var login = Model[i]; + var isClaimed = !string.IsNullOrEmpty(login.ClaimedBy); + + + + + + + + } + +
#EmailPasswordStatusClaimed By
@(i + 1)@login.LoginEmail + @if (isClaimed) + { + •••••••• + } + else + { + Available + } + + @if (isClaimed) + { + + Claimed + + } + else + { + + Available + + } + + @if (isClaimed) + { + @login.ClaimedBy + } + else + { + - + } +
+
+ +
+
+
+
+ Available: @Model.Count(l => string.IsNullOrEmpty(l.ClaimedBy)) +
+
+
+
+ Claimed: @Model.Count(l => !string.IsNullOrEmpty(l.ClaimedBy)) +
+
+
+
+ } + else + { +
+ No login entries available. +
+ } +
+
+ + + +
+
+
+
diff --git a/apps-rps/rps-game-server/Views/Shared/_Layout.cshtml b/apps-rps/rps-game-server/Views/Shared/_Layout.cshtml index 5317175..36ac474 100644 --- a/apps-rps/rps-game-server/Views/Shared/_Layout.cshtml +++ b/apps-rps/rps-game-server/Views/Shared/_Layout.cshtml @@ -17,10 +17,11 @@ Dashboard Results Grid View + Azure Login @if (Context.Session.GetString("AdminAuthenticated") == "true") { - - Admin Panel Logged In + + Settings Logged In } else diff --git a/apps-rps/rps-game-server/login.json b/apps-rps/rps-game-server/login.json new file mode 100644 index 0000000..6e2fd99 --- /dev/null +++ b/apps-rps/rps-game-server/login.json @@ -0,0 +1,27 @@ +[ + { + "loginEmail": "user1@example.com", + "loginPassword": "Password123!", + "claimedBy": "Test User" + }, + { + "loginEmail": "user2@example.com", + "loginPassword": "Password456!", + "claimedBy": null + }, + { + "loginEmail": "user3@example.com", + "loginPassword": "Password789!", + "claimedBy": null + }, + { + "loginEmail": "user4@example.com", + "loginPassword": "PasswordAbc!", + "claimedBy": null + }, + { + "loginEmail": "user5@example.com", + "loginPassword": "PasswordXyz!", + "claimedBy": null + } +] \ No newline at end of file