From a72ffc3f14fa60fadf9a5a43c17046af94da1c7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:13:06 +0000 Subject: [PATCH 1/6] Initial plan From 3543eef41f1c897c8931d52a14d890e9ac07e8b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:20:04 +0000 Subject: [PATCH 2/6] Add .NET 10, central package management, and VS Code debug config Co-authored-by: punkouter26 <121304072+punkouter26@users.noreply.github.com> --- .gitignore | 2 + Directory.Packages.props | 65 ++++++++++++++++++++++ PoFastType.Api/PoFastType.Api.csproj | 41 +++++++------- PoFastType.Client/PoFastType.Client.csproj | 13 +++-- PoFastType.Shared/PoFastType.Shared.csproj | 5 +- PoFastType.Tests/PoFastType.Tests.csproj | 26 +++++---- global.json | 6 ++ 7 files changed, 118 insertions(+), 40 deletions(-) create mode 100644 Directory.Packages.props create mode 100644 global.json diff --git a/.gitignore b/.gitignore index 149cb9a..1229b63 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,8 @@ temp/ # Editor directories and files .vscode/* +!.vscode/launch.json +!.vscode/tasks.json !.vscode/extensions.json .idea .DS_Store diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..cbe75a9 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,65 @@ + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PoFastType.Api/PoFastType.Api.csproj b/PoFastType.Api/PoFastType.Api.csproj index 14a77bf..dcd2524 100644 --- a/PoFastType.Api/PoFastType.Api.csproj +++ b/PoFastType.Api/PoFastType.Api.csproj @@ -1,31 +1,32 @@ ๏ปฟ - net9.0 + net10.0 enable enable 6226cd99-6765-4cd7-8191-b8247a1390fc + - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + + + + + + + + + + + + + diff --git a/PoFastType.Client/PoFastType.Client.csproj b/PoFastType.Client/PoFastType.Client.csproj index 2253aa3..e9400a7 100644 --- a/PoFastType.Client/PoFastType.Client.csproj +++ b/PoFastType.Client/PoFastType.Client.csproj @@ -1,17 +1,18 @@ ๏ปฟ - net9.0 + net10.0 enable enable 40e08d54-7033-46bf-a611-aef34e82c645 + - - - - - + + + + + diff --git a/PoFastType.Shared/PoFastType.Shared.csproj b/PoFastType.Shared/PoFastType.Shared.csproj index d6f0cd2..9367463 100644 --- a/PoFastType.Shared/PoFastType.Shared.csproj +++ b/PoFastType.Shared/PoFastType.Shared.csproj @@ -1,13 +1,14 @@ ๏ปฟ - net9.0 + net10.0 enable enable - + + diff --git a/PoFastType.Tests/PoFastType.Tests.csproj b/PoFastType.Tests/PoFastType.Tests.csproj index c05447d..b069fae 100644 --- a/PoFastType.Tests/PoFastType.Tests.csproj +++ b/PoFastType.Tests/PoFastType.Tests.csproj @@ -1,7 +1,7 @@ ๏ปฟ - net9.0 + net10.0 enable enable false @@ -9,23 +9,25 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + + + diff --git a/global.json b/global.json new file mode 100644 index 0000000..b24aad6 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestPatch" + } +} From 9ac3083a1f19816749299dce70c5e06b492ca95f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:24:29 +0000 Subject: [PATCH 3/6] Reorganize to standard folder structure (/src, /tests, /docs, /scripts) Co-authored-by: punkouter26 <121304072+punkouter26@users.noreply.github.com> --- PoFastType.sln | 8 +- README.md | 137 ++++++++++-------- .../Diagrams}/SIMPLE_class-diagram.mmd | 0 .../Diagrams}/SIMPLE_class-diagram.svg | 0 .../Diagrams}/SIMPLE_component-hierarchy.mmd | 0 .../Diagrams}/SIMPLE_component-hierarchy.svg | 0 .../Diagrams}/SIMPLE_flowchart.mmd | 0 .../Diagrams}/SIMPLE_flowchart.svg | 0 .../Diagrams}/SIMPLE_project-dependency.mmd | 0 .../Diagrams}/SIMPLE_project-dependency.svg | 0 .../Diagrams}/SIMPLE_sequence-diagram.mmd | 0 .../Diagrams}/SIMPLE_sequence-diagram.svg | 0 {Diagrams => docs/Diagrams}/class-diagram.mmd | 0 {Diagrams => docs/Diagrams}/class-diagram.svg | 0 .../Diagrams}/component-hierarchy.mmd | 0 .../Diagrams}/component-hierarchy.svg | 0 {Diagrams => docs/Diagrams}/flowchart.mmd | 0 {Diagrams => docs/Diagrams}/flowchart.svg | 0 .../Diagrams}/project-dependency.mmd | 0 .../Diagrams}/project-dependency.svg | 0 .../Diagrams}/sequence-diagram.mmd | 0 .../Diagrams}/sequence-diagram.svg | 0 PRD.md => docs/PRD.md | 0 docs/README.md | 28 ++++ docs/kql/app-performance.kql | 12 ++ docs/kql/endpoint-performance.kql | 13 ++ docs/kql/error-analysis.kql | 10 ++ docs/kql/storage-health.kql | 12 ++ docs/kql/top-scores.kql | 14 ++ docs/kql/user-activity.kql | 12 ++ scripts/run-coverage.ps1 | 33 +++++ scripts/run-coverage.sh | 30 ++++ scripts/start-azurite.ps1 | 23 +++ scripts/start-azurite.sh | 22 +++ .../Controllers/DiagController.cs | 0 .../Controllers/GameController.cs | 0 .../Controllers/ScoresController.cs | 0 .../Controllers/UserController.cs | 0 .../AzureTableStorageHealthCheck.cs | 0 .../Middleware/GlobalExceptionMiddleware.cs | 0 .../PoFastType.Api}/PoFastType.Api.csproj | 0 .../PoFastType.Api}/Program.cs | 0 .../PoFastType - Web Deploy/profile.arm.json | 0 .../Properties/launchSettings.json | 0 .../PoFastType.Api}/README.md | 0 .../AzureTableGameResultRepository.cs | 0 .../Repositories/IGameResultRepository.cs | 0 .../PoFastType.Api}/Services/GameService.cs | 0 .../Services/HardcodedTextStrategy.cs | 0 .../PoFastType.Api}/Services/IGameService.cs | 0 .../Services/ITextGenerationService.cs | 0 .../Services/ITextGenerationStrategy.cs | 0 .../Services/IUserIdentityService.cs | 0 .../Services/TextGenerationService.cs | 0 .../Services/UserIdentityService.cs | 0 .../appsettings.Development.json | 0 .../appsettings.Production.json | 0 .../PoFastType.Api}/appsettings.json | 0 .../PoFastType.Api}/log.txt | 0 .../PoFastType.Api}/web.config | 0 .../PoFastType.Client}/App.razor | 0 .../Components/ErrorBoundary.razor | 0 .../Components/Navbar_New.razor | 0 .../Layout/MainLayout.razor | 0 .../PoFastType.Client}/Pages/Diag.razor | 0 .../PoFastType.Client}/Pages/Home.razor | 0 .../Pages/Leaderboard.razor | 0 .../PoFastType.Client}/Pages/UserStats.razor | 0 .../PoFastType.Client.csproj | 0 .../PoFastType.Client}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../PoFastType.Client}/README.md | 0 .../Services/GameStateService.cs | 0 .../Services/IUserService.cs | 0 .../Services/UserService.cs | 0 .../PoFastType.Client}/_Imports.razor | 0 .../PoFastType.Client}/wwwroot/app.js | 0 .../wwwroot/appsettings.json | 0 .../wwwroot/css/retro-arcade.css | 0 .../PoFastType.Client}/wwwroot/favicon.png | Bin .../PoFastType.Client}/wwwroot/icon-192.png | Bin .../PoFastType.Client}/wwwroot/index.html | 0 .../wwwroot/sample-data/weather.json | 0 .../PoFastType.Shared}/Models/GameResult.cs | 0 .../Models/LeaderboardEntry.cs | 0 .../Models/UserGameResult.cs | 0 .../PoFastType.Shared}/Models/UserIdentity.cs | 0 .../PoFastType.Shared.csproj | 0 .../PoFastType.Shared}/README.md | 0 .../API/Controllers/DiagControllerTests.cs | 0 .../API/Controllers/GameControllerTests.cs | 0 .../API/Controllers/ScoresControllerTests.cs | 0 .../PoFastType.Tests}/E2E/HomePageE2ETests.cs | 0 .../E2E/LeaderboardPageE2ETests.cs | 0 .../E2E/ResponsiveDesignE2ETests.cs | 0 .../E2E/UserStatsPageE2ETests.cs | 0 .../AzureTableStorageIntegrationTests.cs | 0 .../Integration/Services/GameServiceTests.cs | 0 .../PoFastType.Tests}/PoFastType.Tests.csproj | 4 +- .../PoFastType.Tests}/README.md | 0 .../System/TypingGameSystemTests.cs | 0 .../CustomWebApplicationFactory.cs | 0 .../AzureTableGameResultRepositoryTests.cs | 0 .../Services/HardcodedTextStrategyTests.cs | 0 .../Services/TextGenerationServiceTests.cs | 0 105 files changed, 293 insertions(+), 65 deletions(-) rename {Diagrams => docs/Diagrams}/SIMPLE_class-diagram.mmd (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_class-diagram.svg (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_component-hierarchy.mmd (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_component-hierarchy.svg (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_flowchart.mmd (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_flowchart.svg (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_project-dependency.mmd (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_project-dependency.svg (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_sequence-diagram.mmd (100%) rename {Diagrams => docs/Diagrams}/SIMPLE_sequence-diagram.svg (100%) rename {Diagrams => docs/Diagrams}/class-diagram.mmd (100%) rename {Diagrams => docs/Diagrams}/class-diagram.svg (100%) rename {Diagrams => docs/Diagrams}/component-hierarchy.mmd (100%) rename {Diagrams => docs/Diagrams}/component-hierarchy.svg (100%) rename {Diagrams => docs/Diagrams}/flowchart.mmd (100%) rename {Diagrams => docs/Diagrams}/flowchart.svg (100%) rename {Diagrams => docs/Diagrams}/project-dependency.mmd (100%) rename {Diagrams => docs/Diagrams}/project-dependency.svg (100%) rename {Diagrams => docs/Diagrams}/sequence-diagram.mmd (100%) rename {Diagrams => docs/Diagrams}/sequence-diagram.svg (100%) rename PRD.md => docs/PRD.md (100%) create mode 100644 docs/README.md create mode 100644 docs/kql/app-performance.kql create mode 100644 docs/kql/endpoint-performance.kql create mode 100644 docs/kql/error-analysis.kql create mode 100644 docs/kql/storage-health.kql create mode 100644 docs/kql/top-scores.kql create mode 100644 docs/kql/user-activity.kql create mode 100644 scripts/run-coverage.ps1 create mode 100755 scripts/run-coverage.sh create mode 100644 scripts/start-azurite.ps1 create mode 100755 scripts/start-azurite.sh rename {PoFastType.Api => src/PoFastType.Api}/Controllers/DiagController.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Controllers/GameController.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Controllers/ScoresController.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Controllers/UserController.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/HealthChecks/AzureTableStorageHealthCheck.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Middleware/GlobalExceptionMiddleware.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/PoFastType.Api.csproj (100%) rename {PoFastType.Api => src/PoFastType.Api}/Program.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Properties/ServiceDependencies/PoFastType - Web Deploy/profile.arm.json (100%) rename {PoFastType.Api => src/PoFastType.Api}/Properties/launchSettings.json (100%) rename {PoFastType.Api => src/PoFastType.Api}/README.md (100%) rename {PoFastType.Api => src/PoFastType.Api}/Repositories/AzureTableGameResultRepository.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Repositories/IGameResultRepository.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/GameService.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/HardcodedTextStrategy.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/IGameService.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/ITextGenerationService.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/ITextGenerationStrategy.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/IUserIdentityService.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/TextGenerationService.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/Services/UserIdentityService.cs (100%) rename {PoFastType.Api => src/PoFastType.Api}/appsettings.Development.json (100%) rename {PoFastType.Api => src/PoFastType.Api}/appsettings.Production.json (100%) rename {PoFastType.Api => src/PoFastType.Api}/appsettings.json (100%) rename {PoFastType.Api => src/PoFastType.Api}/log.txt (100%) rename {PoFastType.Api => src/PoFastType.Api}/web.config (100%) rename {PoFastType.Client => src/PoFastType.Client}/App.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/Components/ErrorBoundary.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/Components/Navbar_New.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/Layout/MainLayout.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/Pages/Diag.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/Pages/Home.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/Pages/Leaderboard.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/Pages/UserStats.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/PoFastType.Client.csproj (100%) rename {PoFastType.Client => src/PoFastType.Client}/Program.cs (100%) rename {PoFastType.Client => src/PoFastType.Client}/Properties/launchSettings.json (100%) rename {PoFastType.Client => src/PoFastType.Client}/README.md (100%) rename {PoFastType.Client => src/PoFastType.Client}/Services/GameStateService.cs (100%) rename {PoFastType.Client => src/PoFastType.Client}/Services/IUserService.cs (100%) rename {PoFastType.Client => src/PoFastType.Client}/Services/UserService.cs (100%) rename {PoFastType.Client => src/PoFastType.Client}/_Imports.razor (100%) rename {PoFastType.Client => src/PoFastType.Client}/wwwroot/app.js (100%) rename {PoFastType.Client => src/PoFastType.Client}/wwwroot/appsettings.json (100%) rename {PoFastType.Client => src/PoFastType.Client}/wwwroot/css/retro-arcade.css (100%) rename {PoFastType.Client => src/PoFastType.Client}/wwwroot/favicon.png (100%) rename {PoFastType.Client => src/PoFastType.Client}/wwwroot/icon-192.png (100%) rename {PoFastType.Client => src/PoFastType.Client}/wwwroot/index.html (100%) rename {PoFastType.Client => src/PoFastType.Client}/wwwroot/sample-data/weather.json (100%) rename {PoFastType.Shared => src/PoFastType.Shared}/Models/GameResult.cs (100%) rename {PoFastType.Shared => src/PoFastType.Shared}/Models/LeaderboardEntry.cs (100%) rename {PoFastType.Shared => src/PoFastType.Shared}/Models/UserGameResult.cs (100%) rename {PoFastType.Shared => src/PoFastType.Shared}/Models/UserIdentity.cs (100%) rename {PoFastType.Shared => src/PoFastType.Shared}/PoFastType.Shared.csproj (100%) rename {PoFastType.Shared => src/PoFastType.Shared}/README.md (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/API/Controllers/DiagControllerTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/API/Controllers/GameControllerTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/API/Controllers/ScoresControllerTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/E2E/HomePageE2ETests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/E2E/LeaderboardPageE2ETests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/E2E/ResponsiveDesignE2ETests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/E2E/UserStatsPageE2ETests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/Integration/AzureTableStorageIntegrationTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/Integration/Services/GameServiceTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/PoFastType.Tests.csproj (89%) rename {PoFastType.Tests => tests/PoFastType.Tests}/README.md (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/System/TypingGameSystemTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/TestHelpers/CustomWebApplicationFactory.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/Unit/Repositories/AzureTableGameResultRepositoryTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/Unit/Services/HardcodedTextStrategyTests.cs (100%) rename {PoFastType.Tests => tests/PoFastType.Tests}/Unit/Services/TextGenerationServiceTests.cs (100%) diff --git a/PoFastType.sln b/PoFastType.sln index dee0bbb..e86da96 100644 --- a/PoFastType.sln +++ b/PoFastType.sln @@ -3,13 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Api", "PoFastType.Api\PoFastType.Api.csproj", "{FBD15D2B-0C27-4D6C-8BBA-17EF97728C0C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Api", "src\PoFastType.Api\PoFastType.Api.csproj", "{FBD15D2B-0C27-4D6C-8BBA-17EF97728C0C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Client", "PoFastType.Client\PoFastType.Client.csproj", "{42CFE93B-C8D6-4143-BB8A-60B9A5113A92}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Client", "src\PoFastType.Client\PoFastType.Client.csproj", "{42CFE93B-C8D6-4143-BB8A-60B9A5113A92}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Shared", "PoFastType.Shared\PoFastType.Shared.csproj", "{1C08E782-878D-43C1-99E5-579C9C223A51}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Shared", "src\PoFastType.Shared\PoFastType.Shared.csproj", "{1C08E782-878D-43C1-99E5-579C9C223A51}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Tests", "PoFastType.Tests\PoFastType.Tests.csproj", "{C473E3F9-0DF3-4C4A-B79E-6FA759132CD0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoFastType.Tests", "tests\PoFastType.Tests\PoFastType.Tests.csproj", "{C473E3F9-0DF3-4C4A-B79E-6FA759132CD0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/README.md b/README.md index e2ccd03..b6f2c36 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CD](https://github.com/YOUR_USERNAME/PoFastType/actions/workflows/cd.yml/badge.svg)](https://github.com/YOUR_USERNAME/PoFastType/actions/workflows/cd.yml) [![PR Validation](https://github.com/YOUR_USERNAME/PoFastType/actions/workflows/pr-validation.yml/badge.svg)](https://github.com/YOUR_USERNAME/PoFastType/actions/workflows/pr-validation.yml) -A modern typing speed test application built with .NET 9 and Blazor WebAssembly. Test your typing speed, track your progress, and compete on the leaderboard! +A modern typing speed test application built with .NET 10 and Blazor WebAssembly. Test your typing speed, track your progress, and compete on the leaderboard! ## ๐Ÿš€ Features @@ -12,34 +12,36 @@ A modern typing speed test application built with .NET 9 and Blazor WebAssembly. - **Personal Statistics** - Track your WPM, accuracy, and improvement over time - **Global Leaderboard** - Compete with other users for the top spot - **Responsive Design** - Works seamlessly on desktop and mobile devices -- **Azure-Powered** - Scalable infrastructure with Azure Table Storage and Container Apps +- **Azure-Powered** - Scalable infrastructure with Azure Table Storage and App Service ## ๐Ÿ“š Documentation -- **[PRD.md](PRD.md)** - Product Requirements Document with detailed UI component specifications +- **[docs/PRD.md](docs/PRD.md)** - Product Requirements Document with detailed UI component specifications - **[AGENTS.md](AGENTS.md)** - AI Coding Agent Guide with project conventions and gotchas -- **[PoFastType.Api/README.md](PoFastType.Api/README.md)** - Backend API documentation -- **[PoFastType.Client/README.md](PoFastType.Client/README.md)** - Frontend Blazor documentation -- **[PoFastType.Shared/README.md](PoFastType.Shared/README.md)** - Shared models and DTOs documentation -- **[PoFastType.Tests/README.md](PoFastType.Tests/README.md)** - Comprehensive test suite documentation +- **[docs/README.md](docs/README.md)** - Documentation index +- **[docs/kql/](docs/kql/)** - KQL query library for Application Insights monitoring +- **[src/PoFastType.Api/README.md](src/PoFastType.Api/README.md)** - Backend API documentation +- **[src/PoFastType.Client/README.md](src/PoFastType.Client/README.md)** - Frontend Blazor documentation +- **[src/PoFastType.Shared/README.md](src/PoFastType.Shared/README.md)** - Shared models and DTOs documentation +- **[tests/PoFastType.Tests/README.md](tests/PoFastType.Tests/README.md)** - Comprehensive test suite documentation ## ๐Ÿ—๏ธ Architecture This project follows **Vertical Slice Architecture** with **Clean Architecture principles**: -- **PoFastType.Api** - ASP.NET Core Web API backend -- **PoFastType.Client** - Blazor WebAssembly frontend (hosted in API) -- **PoFastType.Shared** - Shared models and contracts -- **PoFastType.Tests** - Comprehensive test suite (96 tests, 31.34% coverage) +- **src/PoFastType.Api** - ASP.NET Core Web API backend +- **src/PoFastType.Client** - Blazor WebAssembly frontend (hosted in API) +- **src/PoFastType.Shared** - Shared models and contracts +- **tests/PoFastType.Tests** - Comprehensive test suite (96 tests, 31.34% coverage) ### Technology Stack - **Frontend:** Blazor WebAssembly, Radzen UI Components -- **Backend:** .NET 9, ASP.NET Core Web API -- **Database:** Azure Table Storage -- **Monitoring:** Application Insights, Serilog +- **Backend:** .NET 10, ASP.NET Core Web API +- **Database:** Azure Table Storage (Azurite for local development) +- **Monitoring:** Application Insights, Serilog, OpenTelemetry - **CI/CD:** GitHub Actions with Azure Developer CLI -- **Infrastructure:** Azure App Service (F1 Free Tier) + Bicep IaC +- **Infrastructure:** Azure App Service + Bicep IaC ### Architecture Diagrams @@ -47,50 +49,50 @@ This project follows **Vertical Slice Architecture** with **Clean Architecture p ๐Ÿ“Š Click to view architecture diagrams #### Project Dependencies -![Project Dependencies](Diagrams/project-dependency.svg) +![Project Dependencies](docs/Diagrams/project-dependency.svg)
Simple version -![Simple Project Dependencies](Diagrams/SIMPLE_project-dependency.svg) +![Simple Project Dependencies](docs/Diagrams/SIMPLE_project-dependency.svg)
#### Domain Model (Class Diagram) -![Class Diagram](Diagrams/class-diagram.svg) +![Class Diagram](docs/Diagrams/class-diagram.svg)
Simple version -![Simple Class Diagram](Diagrams/SIMPLE_class-diagram.svg) +![Simple Class Diagram](docs/Diagrams/SIMPLE_class-diagram.svg)
#### API Call Flow (Sequence Diagram) -![Sequence Diagram](Diagrams/sequence-diagram.svg) +![Sequence Diagram](docs/Diagrams/sequence-diagram.svg)
Simple version -![Simple Sequence Diagram](Diagrams/SIMPLE_sequence-diagram.svg) +![Simple Sequence Diagram](docs/Diagrams/SIMPLE_sequence-diagram.svg)
#### Game Play Use Case (Flowchart) -![Flowchart](Diagrams/flowchart.svg) +![Flowchart](docs/Diagrams/flowchart.svg)
Simple version -![Simple Flowchart](Diagrams/SIMPLE_flowchart.svg) +![Simple Flowchart](docs/Diagrams/SIMPLE_flowchart.svg)
#### Blazor Component Hierarchy -![Component Hierarchy](Diagrams/component-hierarchy.svg) +![Component Hierarchy](docs/Diagrams/component-hierarchy.svg)
Simple version -![Simple Component Hierarchy](Diagrams/SIMPLE_component-hierarchy.svg) +![Simple Component Hierarchy](docs/Diagrams/SIMPLE_component-hierarchy.svg)
## ๐Ÿ“‹ Prerequisites -- [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) +- [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) - [Azure Developer CLI (azd)](https://aka.ms/azure-dev/install) - [Azure CLI (az)](https://docs.microsoft.com/cli/azure/install-azure-cli) - [Node.js](https://nodejs.org/) (for Azurite) @@ -114,7 +116,7 @@ cd PoFastType ### 3. Run the application ```powershell -dotnet run --project PoFastType.Api +dotnet run --project src/PoFastType.Api ``` Or press **F5** in Visual Studio/VS Code. @@ -123,7 +125,7 @@ Or press **F5** in Visual Studio/VS Code. - **App:** https://localhost:5001 - **Swagger API:** https://localhost:5001/swagger -- **Health Check:** https://localhost:5001/api/diag/health +- **Health Check:** https://localhost:5001/api/health - **Diagnostics:** https://localhost:5001/diag ## ๐Ÿงช Testing @@ -275,37 +277,54 @@ The application includes comprehensive health checks: ``` PoFastType/ โ”œโ”€โ”€ .github/ -โ”‚ โ””โ”€โ”€ workflows/ # GitHub Actions CI/CD workflows -โ”‚ โ”œโ”€โ”€ ci.yml # Continuous Integration (build, test, quality checks) -โ”‚ โ”œโ”€โ”€ cd.yml # Continuous Deployment (deploy to Azure) -โ”‚ โ””โ”€โ”€ pr-validation.yml # Pull Request validation -โ”œโ”€โ”€ Diagrams/ # Mermaid architecture diagrams (.mmd and .svg) -โ”œโ”€โ”€ infra/ # Bicep infrastructure templates -โ”‚ โ”œโ”€โ”€ main.bicep # Main infrastructure template -โ”‚ โ”œโ”€โ”€ main.parameters.json # Infrastructure parameters -โ”‚ โ””โ”€โ”€ resources.bicep # Azure resources (App Service, Storage) -โ”œโ”€โ”€ scripts/ # Automation scripts -โ”‚ โ””โ”€โ”€ start-azurite.ps1 # Start local Azure Storage emulator -โ”œโ”€โ”€ PoFastType.Api/ # Backend API project -โ”‚ โ”œโ”€โ”€ Controllers/ # API controllers (Game, Scores, Diag, User) -โ”‚ โ”œโ”€โ”€ Services/ # Business logic services -โ”‚ โ”œโ”€โ”€ Repositories/ # Data access layer (Azure Table Storage) -โ”‚ โ””โ”€โ”€ Middleware/ # Global exception handling (RFC 7807) -โ”œโ”€โ”€ PoFastType.Client/ # Blazor WebAssembly frontend -โ”‚ โ”œโ”€โ”€ Pages/ # Razor pages (Home, Leaderboard, UserStats, Diag) -โ”‚ โ”œโ”€โ”€ Components/ # Reusable UI components (Navbar, ErrorBoundary) -โ”‚ โ”œโ”€โ”€ Layout/ # Application layout (MainLayout) -โ”‚ โ””โ”€โ”€ Services/ # Frontend services (GameState, UserService) -โ”œโ”€โ”€ PoFastType.Shared/ # Shared models and DTOs -โ”‚ โ””โ”€โ”€ Models/ # Domain models (GameResult, UserIdentity, etc.) -โ”œโ”€โ”€ PoFastType.Tests/ # Test projects (96 tests, 31.34% coverage) -โ”‚ โ”œโ”€โ”€ Unit/ # Unit tests (services, repositories) -โ”‚ โ”œโ”€โ”€ Integration/ # Integration tests (with Azurite) -โ”‚ โ”œโ”€โ”€ API/ # API endpoint tests -โ”‚ โ”œโ”€โ”€ E2E/ # End-to-end tests (Playwright) -โ”‚ โ””โ”€โ”€ System/ # System-level tests -โ”œโ”€โ”€ PRD.md # Product Requirements Document -โ”œโ”€โ”€ AGENTS.md # AI Coding Agent Guide +โ”‚ โ””โ”€โ”€ workflows/ # GitHub Actions CI/CD workflows +โ”œโ”€โ”€ .vscode/ +โ”‚ โ”œโ”€โ”€ launch.json # F5 debug configuration +โ”‚ โ””โ”€โ”€ tasks.json # Build tasks +โ”œโ”€โ”€ docs/ # Documentation +โ”‚ โ”œโ”€โ”€ Diagrams/ # Mermaid architecture diagrams (.mmd and .svg) +โ”‚ โ”œโ”€โ”€ kql/ # KQL queries for Application Insights +โ”‚ โ”œโ”€โ”€ coverage/ # Code coverage reports +โ”‚ โ”œโ”€โ”€ PRD.md # Product Requirements Document +โ”‚ โ””โ”€โ”€ README.md # Documentation index +โ”œโ”€โ”€ infra/ # Bicep infrastructure templates +โ”‚ โ”œโ”€โ”€ main.bicep # Main infrastructure template +โ”‚ โ”œโ”€โ”€ main.parameters.json # Infrastructure parameters +โ”‚ โ””โ”€โ”€ resources.bicep # Azure resources (App Insights, App Service, Storage) +โ”œโ”€โ”€ scripts/ # Automation scripts +โ”‚ โ”œโ”€โ”€ start-azurite.ps1 # Start local Azure Storage emulator (Windows) +โ”‚ โ”œโ”€โ”€ start-azurite.sh # Start local Azure Storage emulator (Linux/macOS) +โ”‚ โ”œโ”€โ”€ run-coverage.ps1 # Run code coverage analysis (Windows) +โ”‚ โ””โ”€โ”€ run-coverage.sh # Run code coverage analysis (Linux/macOS) +โ”œโ”€โ”€ src/ # Source code +โ”‚ โ”œโ”€โ”€ PoFastType.Api/ # Backend API project +โ”‚ โ”‚ โ”œโ”€โ”€ Controllers/ # API controllers (Game, Scores, Diag, User) +โ”‚ โ”‚ โ”œโ”€โ”€ Services/ # Business logic services +โ”‚ โ”‚ โ”œโ”€โ”€ Repositories/ # Data access layer (Azure Table Storage) +โ”‚ โ”‚ โ”œโ”€โ”€ Middleware/ # Global exception handling (RFC 7807) +โ”‚ โ”‚ โ””โ”€โ”€ HealthChecks/ # Health check implementations +โ”‚ โ”œโ”€โ”€ PoFastType.Client/ # Blazor WebAssembly frontend +โ”‚ โ”‚ โ”œโ”€โ”€ Pages/ # Razor pages (Home, Leaderboard, UserStats, Diag) +โ”‚ โ”‚ โ”œโ”€โ”€ Components/ # Reusable UI components (Navbar, ErrorBoundary) +โ”‚ โ”‚ โ”œโ”€โ”€ Layout/ # Application layout (MainLayout) +โ”‚ โ”‚ โ””โ”€โ”€ Services/ # Frontend services (GameState, UserService) +โ”‚ โ””โ”€โ”€ PoFastType.Shared/ # Shared models and DTOs +โ”‚ โ””โ”€โ”€ Models/ # Domain models (GameResult, UserIdentity, etc.) +โ”œโ”€โ”€ tests/ # Test projects +โ”‚ โ””โ”€โ”€ PoFastType.Tests/ # Comprehensive test suite (96 tests, 31.34% coverage) +โ”‚ โ”œโ”€โ”€ Unit/ # Unit tests (services, repositories) +โ”‚ โ”œโ”€โ”€ Integration/ # Integration tests (with Azurite) +โ”‚ โ”œโ”€โ”€ API/ # API endpoint tests +โ”‚ โ”œโ”€โ”€ E2E/ # End-to-end tests (Playwright) +โ”‚ โ””โ”€โ”€ System/ # System-level tests +โ”œโ”€โ”€ Directory.Packages.props # Centralized package management +โ”œโ”€โ”€ global.json # .NET SDK version lock (10.0.100) +โ”œโ”€โ”€ PoFastType.sln # Solution file +โ”œโ”€โ”€ PoFastType.http # API test collection +โ”œโ”€โ”€ AGENTS.md # AI Coding Agent Guide +โ”œโ”€โ”€ README.md # This file +โ””โ”€โ”€ azure.yaml # Azure Developer CLI configuration +``` โ””โ”€โ”€ azure.yaml # Azure Developer CLI configuration ``` diff --git a/Diagrams/SIMPLE_class-diagram.mmd b/docs/Diagrams/SIMPLE_class-diagram.mmd similarity index 100% rename from Diagrams/SIMPLE_class-diagram.mmd rename to docs/Diagrams/SIMPLE_class-diagram.mmd diff --git a/Diagrams/SIMPLE_class-diagram.svg b/docs/Diagrams/SIMPLE_class-diagram.svg similarity index 100% rename from Diagrams/SIMPLE_class-diagram.svg rename to docs/Diagrams/SIMPLE_class-diagram.svg diff --git a/Diagrams/SIMPLE_component-hierarchy.mmd b/docs/Diagrams/SIMPLE_component-hierarchy.mmd similarity index 100% rename from Diagrams/SIMPLE_component-hierarchy.mmd rename to docs/Diagrams/SIMPLE_component-hierarchy.mmd diff --git a/Diagrams/SIMPLE_component-hierarchy.svg b/docs/Diagrams/SIMPLE_component-hierarchy.svg similarity index 100% rename from Diagrams/SIMPLE_component-hierarchy.svg rename to docs/Diagrams/SIMPLE_component-hierarchy.svg diff --git a/Diagrams/SIMPLE_flowchart.mmd b/docs/Diagrams/SIMPLE_flowchart.mmd similarity index 100% rename from Diagrams/SIMPLE_flowchart.mmd rename to docs/Diagrams/SIMPLE_flowchart.mmd diff --git a/Diagrams/SIMPLE_flowchart.svg b/docs/Diagrams/SIMPLE_flowchart.svg similarity index 100% rename from Diagrams/SIMPLE_flowchart.svg rename to docs/Diagrams/SIMPLE_flowchart.svg diff --git a/Diagrams/SIMPLE_project-dependency.mmd b/docs/Diagrams/SIMPLE_project-dependency.mmd similarity index 100% rename from Diagrams/SIMPLE_project-dependency.mmd rename to docs/Diagrams/SIMPLE_project-dependency.mmd diff --git a/Diagrams/SIMPLE_project-dependency.svg b/docs/Diagrams/SIMPLE_project-dependency.svg similarity index 100% rename from Diagrams/SIMPLE_project-dependency.svg rename to docs/Diagrams/SIMPLE_project-dependency.svg diff --git a/Diagrams/SIMPLE_sequence-diagram.mmd b/docs/Diagrams/SIMPLE_sequence-diagram.mmd similarity index 100% rename from Diagrams/SIMPLE_sequence-diagram.mmd rename to docs/Diagrams/SIMPLE_sequence-diagram.mmd diff --git a/Diagrams/SIMPLE_sequence-diagram.svg b/docs/Diagrams/SIMPLE_sequence-diagram.svg similarity index 100% rename from Diagrams/SIMPLE_sequence-diagram.svg rename to docs/Diagrams/SIMPLE_sequence-diagram.svg diff --git a/Diagrams/class-diagram.mmd b/docs/Diagrams/class-diagram.mmd similarity index 100% rename from Diagrams/class-diagram.mmd rename to docs/Diagrams/class-diagram.mmd diff --git a/Diagrams/class-diagram.svg b/docs/Diagrams/class-diagram.svg similarity index 100% rename from Diagrams/class-diagram.svg rename to docs/Diagrams/class-diagram.svg diff --git a/Diagrams/component-hierarchy.mmd b/docs/Diagrams/component-hierarchy.mmd similarity index 100% rename from Diagrams/component-hierarchy.mmd rename to docs/Diagrams/component-hierarchy.mmd diff --git a/Diagrams/component-hierarchy.svg b/docs/Diagrams/component-hierarchy.svg similarity index 100% rename from Diagrams/component-hierarchy.svg rename to docs/Diagrams/component-hierarchy.svg diff --git a/Diagrams/flowchart.mmd b/docs/Diagrams/flowchart.mmd similarity index 100% rename from Diagrams/flowchart.mmd rename to docs/Diagrams/flowchart.mmd diff --git a/Diagrams/flowchart.svg b/docs/Diagrams/flowchart.svg similarity index 100% rename from Diagrams/flowchart.svg rename to docs/Diagrams/flowchart.svg diff --git a/Diagrams/project-dependency.mmd b/docs/Diagrams/project-dependency.mmd similarity index 100% rename from Diagrams/project-dependency.mmd rename to docs/Diagrams/project-dependency.mmd diff --git a/Diagrams/project-dependency.svg b/docs/Diagrams/project-dependency.svg similarity index 100% rename from Diagrams/project-dependency.svg rename to docs/Diagrams/project-dependency.svg diff --git a/Diagrams/sequence-diagram.mmd b/docs/Diagrams/sequence-diagram.mmd similarity index 100% rename from Diagrams/sequence-diagram.mmd rename to docs/Diagrams/sequence-diagram.mmd diff --git a/Diagrams/sequence-diagram.svg b/docs/Diagrams/sequence-diagram.svg similarity index 100% rename from Diagrams/sequence-diagram.svg rename to docs/Diagrams/sequence-diagram.svg diff --git a/PRD.md b/docs/PRD.md similarity index 100% rename from PRD.md rename to docs/PRD.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d645aba --- /dev/null +++ b/docs/README.md @@ -0,0 +1,28 @@ +# PoFastType Documentation + +This folder contains all documentation for the PoFastType application. + +## Contents + +- **[PRD.md](PRD.md)** - Product Requirements Document +- **[Diagrams/](Diagrams/)** - Mermaid architecture diagrams (.mmd and .svg) +- **[coverage/](coverage/)** - Code coverage reports +- **[kql/](kql/)** - KQL query library for Application Insights + +## Architecture Diagrams + +The Diagrams folder contains: +- Project dependencies +- Domain model (class diagram) +- API call flow (sequence diagram) +- Game play use case (flowchart) +- Blazor component hierarchy + +## Code Coverage + +Code coverage reports are generated during test runs and stored in the coverage folder. +Target: 80% minimum line coverage for all new business logic. + +## KQL Queries + +The kql folder contains essential Kusto Query Language (KQL) queries for monitoring the application in Azure Application Insights. diff --git a/docs/kql/app-performance.kql b/docs/kql/app-performance.kql new file mode 100644 index 0000000..50bc6d7 --- /dev/null +++ b/docs/kql/app-performance.kql @@ -0,0 +1,12 @@ +// Application Performance Overview +// Shows request count, duration, and success rate over time +requests +| where timestamp > ago(24h) +| summarize + RequestCount = count(), + AvgDuration = avg(duration), + P95Duration = percentile(duration, 95), + SuccessRate = countif(success == true) * 100.0 / count() + by bin(timestamp, 5m) +| order by timestamp desc +| render timechart diff --git a/docs/kql/endpoint-performance.kql b/docs/kql/endpoint-performance.kql new file mode 100644 index 0000000..a5c02b8 --- /dev/null +++ b/docs/kql/endpoint-performance.kql @@ -0,0 +1,13 @@ +// API Endpoint Performance +// Shows performance metrics for each API endpoint +requests +| where timestamp > ago(24h) +| summarize + Count = count(), + AvgDuration = avg(duration), + P50 = percentile(duration, 50), + P95 = percentile(duration, 95), + P99 = percentile(duration, 99), + FailureRate = countif(success == false) * 100.0 / count() + by name +| order by Count desc diff --git a/docs/kql/error-analysis.kql b/docs/kql/error-analysis.kql new file mode 100644 index 0000000..3f42689 --- /dev/null +++ b/docs/kql/error-analysis.kql @@ -0,0 +1,10 @@ +// Error Analysis +// Shows exceptions and failed requests +union exceptions, requests +| where timestamp > ago(24h) +| where success == false or itemType == "exception" +| summarize + ErrorCount = count(), + Sample = any(message) + by problemId, type +| order by ErrorCount desc diff --git a/docs/kql/storage-health.kql b/docs/kql/storage-health.kql new file mode 100644 index 0000000..3e98fbb --- /dev/null +++ b/docs/kql/storage-health.kql @@ -0,0 +1,12 @@ +// Azure Storage Health +// Monitors dependency calls to Azure Storage +dependencies +| where timestamp > ago(1h) +| where type == "Azure table" +| summarize + CallCount = count(), + AvgDuration = avg(duration), + FailureCount = countif(success == false), + SuccessRate = countif(success == true) * 100.0 / count() + by target, name +| order by FailureCount desc diff --git a/docs/kql/top-scores.kql b/docs/kql/top-scores.kql new file mode 100644 index 0000000..7438136 --- /dev/null +++ b/docs/kql/top-scores.kql @@ -0,0 +1,14 @@ +// Top Game Scores +// Shows top typing speeds and accuracy +customMetrics +| where timestamp > ago(7d) +| where name in ("WordsPerMinute", "Accuracy") +| extend userId = tostring(customDimensions.UserId) +| summarize + AvgWPM = avgif(value, name == "WordsPerMinute"), + MaxWPM = maxif(value, name == "WordsPerMinute"), + AvgAccuracy = avgif(value, name == "Accuracy") + by userId +| where AvgWPM > 0 +| order by MaxWPM desc +| take 50 diff --git a/docs/kql/user-activity.kql b/docs/kql/user-activity.kql new file mode 100644 index 0000000..5d785c5 --- /dev/null +++ b/docs/kql/user-activity.kql @@ -0,0 +1,12 @@ +// User Activity Tracking +// Shows unique users and their game sessions +customEvents +| where timestamp > ago(7d) +| where name in ("GameStarted", "GameCompleted") +| extend userId = tostring(customDimensions.UserId) +| summarize + SessionCount = dcount(operation_Id), + EventCount = count() + by userId, name +| order by SessionCount desc +| take 100 diff --git a/scripts/run-coverage.ps1 b/scripts/run-coverage.ps1 new file mode 100644 index 0000000..ae942a1 --- /dev/null +++ b/scripts/run-coverage.ps1 @@ -0,0 +1,33 @@ +# Run code coverage analysis + +Write-Host "Running code coverage analysis..." -ForegroundColor Green + +# Clean previous coverage data +if (Test-Path "docs/coverage") { + Remove-Item -Recurse -Force "docs/coverage/*" +} + +# Run tests with coverage collection +dotnet test --collect:"XPlat Code Coverage" --results-directory ./docs/coverage/raw + +# Generate HTML report (requires reportgenerator tool) +$reportGeneratorInstalled = Get-Command reportgenerator -ErrorAction SilentlyContinue + +if ($reportGeneratorInstalled) { + reportgenerator ` + -reports:"docs/coverage/raw/**/coverage.cobertura.xml" ` + -targetdir:"docs/coverage" ` + -reporttypes:"Html;TextSummary" + + Write-Host "" + Write-Host "Coverage report generated at: docs/coverage/index.html" -ForegroundColor Green + Get-Content "docs/coverage/Summary.txt" +} +else { + Write-Host "" + Write-Host "Install reportgenerator for HTML reports:" -ForegroundColor Yellow + Write-Host "dotnet tool install -g dotnet-reportgenerator-globaltool" -ForegroundColor White +} + +Write-Host "" +Write-Host "Coverage analysis complete!" -ForegroundColor Green diff --git a/scripts/run-coverage.sh b/scripts/run-coverage.sh new file mode 100755 index 0000000..50313cf --- /dev/null +++ b/scripts/run-coverage.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Run code coverage analysis + +echo "Running code coverage analysis..." + +# Clean previous coverage data +rm -rf docs/coverage/* + +# Run tests with coverage collection +dotnet test --collect:"XPlat Code Coverage" --results-directory ./docs/coverage/raw + +# Generate HTML report (requires reportgenerator tool) +if command -v reportgenerator &> /dev/null +then + reportgenerator \ + -reports:"docs/coverage/raw/**/coverage.cobertura.xml" \ + -targetdir:"docs/coverage" \ + -reporttypes:"Html;TextSummary" + + echo "" + echo "Coverage report generated at: docs/coverage/index.html" + cat docs/coverage/Summary.txt +else + echo "" + echo "Install reportgenerator for HTML reports:" + echo "dotnet tool install -g dotnet-reportgenerator-globaltool" +fi + +echo "" +echo "Coverage analysis complete!" diff --git a/scripts/start-azurite.ps1 b/scripts/start-azurite.ps1 new file mode 100644 index 0000000..ad0ee00 --- /dev/null +++ b/scripts/start-azurite.ps1 @@ -0,0 +1,23 @@ +# Start Azurite for local Azure Storage emulation + +Write-Host "Starting Azurite (Azure Storage Emulator)..." -ForegroundColor Green + +# Check if Azurite is installed +$azuriteInstalled = Get-Command azurite -ErrorAction SilentlyContinue + +if (-not $azuriteInstalled) { + Write-Host "Azurite is not installed. Installing via npm..." -ForegroundColor Yellow + npm install -g azurite +} + +# Start Azurite with default settings +# - Blob storage on port 10000 +# - Queue storage on port 10001 +# - Table storage on port 10002 +Write-Host "Starting Azurite..." -ForegroundColor Cyan +Start-Process -FilePath "azurite" -ArgumentList "--silent", "--location", "azurite-data", "--debug", "azurite-debug.log" + +Write-Host "Azurite started successfully!" -ForegroundColor Green +Write-Host "Blob endpoint: http://127.0.0.1:10000" -ForegroundColor White +Write-Host "Queue endpoint: http://127.0.0.1:10001" -ForegroundColor White +Write-Host "Table endpoint: http://127.0.0.1:10002" -ForegroundColor White diff --git a/scripts/start-azurite.sh b/scripts/start-azurite.sh new file mode 100755 index 0000000..af211b0 --- /dev/null +++ b/scripts/start-azurite.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Start Azurite for local Azure Storage emulation + +echo "Starting Azurite (Azure Storage Emulator)..." + +# Check if Azurite is installed +if ! command -v azurite &> /dev/null +then + echo "Azurite is not installed. Installing via npm..." + npm install -g azurite +fi + +# Start Azurite with default settings +# - Blob storage on port 10000 +# - Queue storage on port 10001 +# - Table storage on port 10002 +azurite --silent --location azurite-data --debug azurite-debug.log + +echo "Azurite started successfully!" +echo "Blob endpoint: http://127.0.0.1:10000" +echo "Queue endpoint: http://127.0.0.1:10001" +echo "Table endpoint: http://127.0.0.1:10002" diff --git a/PoFastType.Api/Controllers/DiagController.cs b/src/PoFastType.Api/Controllers/DiagController.cs similarity index 100% rename from PoFastType.Api/Controllers/DiagController.cs rename to src/PoFastType.Api/Controllers/DiagController.cs diff --git a/PoFastType.Api/Controllers/GameController.cs b/src/PoFastType.Api/Controllers/GameController.cs similarity index 100% rename from PoFastType.Api/Controllers/GameController.cs rename to src/PoFastType.Api/Controllers/GameController.cs diff --git a/PoFastType.Api/Controllers/ScoresController.cs b/src/PoFastType.Api/Controllers/ScoresController.cs similarity index 100% rename from PoFastType.Api/Controllers/ScoresController.cs rename to src/PoFastType.Api/Controllers/ScoresController.cs diff --git a/PoFastType.Api/Controllers/UserController.cs b/src/PoFastType.Api/Controllers/UserController.cs similarity index 100% rename from PoFastType.Api/Controllers/UserController.cs rename to src/PoFastType.Api/Controllers/UserController.cs diff --git a/PoFastType.Api/HealthChecks/AzureTableStorageHealthCheck.cs b/src/PoFastType.Api/HealthChecks/AzureTableStorageHealthCheck.cs similarity index 100% rename from PoFastType.Api/HealthChecks/AzureTableStorageHealthCheck.cs rename to src/PoFastType.Api/HealthChecks/AzureTableStorageHealthCheck.cs diff --git a/PoFastType.Api/Middleware/GlobalExceptionMiddleware.cs b/src/PoFastType.Api/Middleware/GlobalExceptionMiddleware.cs similarity index 100% rename from PoFastType.Api/Middleware/GlobalExceptionMiddleware.cs rename to src/PoFastType.Api/Middleware/GlobalExceptionMiddleware.cs diff --git a/PoFastType.Api/PoFastType.Api.csproj b/src/PoFastType.Api/PoFastType.Api.csproj similarity index 100% rename from PoFastType.Api/PoFastType.Api.csproj rename to src/PoFastType.Api/PoFastType.Api.csproj diff --git a/PoFastType.Api/Program.cs b/src/PoFastType.Api/Program.cs similarity index 100% rename from PoFastType.Api/Program.cs rename to src/PoFastType.Api/Program.cs diff --git a/PoFastType.Api/Properties/ServiceDependencies/PoFastType - Web Deploy/profile.arm.json b/src/PoFastType.Api/Properties/ServiceDependencies/PoFastType - Web Deploy/profile.arm.json similarity index 100% rename from PoFastType.Api/Properties/ServiceDependencies/PoFastType - Web Deploy/profile.arm.json rename to src/PoFastType.Api/Properties/ServiceDependencies/PoFastType - Web Deploy/profile.arm.json diff --git a/PoFastType.Api/Properties/launchSettings.json b/src/PoFastType.Api/Properties/launchSettings.json similarity index 100% rename from PoFastType.Api/Properties/launchSettings.json rename to src/PoFastType.Api/Properties/launchSettings.json diff --git a/PoFastType.Api/README.md b/src/PoFastType.Api/README.md similarity index 100% rename from PoFastType.Api/README.md rename to src/PoFastType.Api/README.md diff --git a/PoFastType.Api/Repositories/AzureTableGameResultRepository.cs b/src/PoFastType.Api/Repositories/AzureTableGameResultRepository.cs similarity index 100% rename from PoFastType.Api/Repositories/AzureTableGameResultRepository.cs rename to src/PoFastType.Api/Repositories/AzureTableGameResultRepository.cs diff --git a/PoFastType.Api/Repositories/IGameResultRepository.cs b/src/PoFastType.Api/Repositories/IGameResultRepository.cs similarity index 100% rename from PoFastType.Api/Repositories/IGameResultRepository.cs rename to src/PoFastType.Api/Repositories/IGameResultRepository.cs diff --git a/PoFastType.Api/Services/GameService.cs b/src/PoFastType.Api/Services/GameService.cs similarity index 100% rename from PoFastType.Api/Services/GameService.cs rename to src/PoFastType.Api/Services/GameService.cs diff --git a/PoFastType.Api/Services/HardcodedTextStrategy.cs b/src/PoFastType.Api/Services/HardcodedTextStrategy.cs similarity index 100% rename from PoFastType.Api/Services/HardcodedTextStrategy.cs rename to src/PoFastType.Api/Services/HardcodedTextStrategy.cs diff --git a/PoFastType.Api/Services/IGameService.cs b/src/PoFastType.Api/Services/IGameService.cs similarity index 100% rename from PoFastType.Api/Services/IGameService.cs rename to src/PoFastType.Api/Services/IGameService.cs diff --git a/PoFastType.Api/Services/ITextGenerationService.cs b/src/PoFastType.Api/Services/ITextGenerationService.cs similarity index 100% rename from PoFastType.Api/Services/ITextGenerationService.cs rename to src/PoFastType.Api/Services/ITextGenerationService.cs diff --git a/PoFastType.Api/Services/ITextGenerationStrategy.cs b/src/PoFastType.Api/Services/ITextGenerationStrategy.cs similarity index 100% rename from PoFastType.Api/Services/ITextGenerationStrategy.cs rename to src/PoFastType.Api/Services/ITextGenerationStrategy.cs diff --git a/PoFastType.Api/Services/IUserIdentityService.cs b/src/PoFastType.Api/Services/IUserIdentityService.cs similarity index 100% rename from PoFastType.Api/Services/IUserIdentityService.cs rename to src/PoFastType.Api/Services/IUserIdentityService.cs diff --git a/PoFastType.Api/Services/TextGenerationService.cs b/src/PoFastType.Api/Services/TextGenerationService.cs similarity index 100% rename from PoFastType.Api/Services/TextGenerationService.cs rename to src/PoFastType.Api/Services/TextGenerationService.cs diff --git a/PoFastType.Api/Services/UserIdentityService.cs b/src/PoFastType.Api/Services/UserIdentityService.cs similarity index 100% rename from PoFastType.Api/Services/UserIdentityService.cs rename to src/PoFastType.Api/Services/UserIdentityService.cs diff --git a/PoFastType.Api/appsettings.Development.json b/src/PoFastType.Api/appsettings.Development.json similarity index 100% rename from PoFastType.Api/appsettings.Development.json rename to src/PoFastType.Api/appsettings.Development.json diff --git a/PoFastType.Api/appsettings.Production.json b/src/PoFastType.Api/appsettings.Production.json similarity index 100% rename from PoFastType.Api/appsettings.Production.json rename to src/PoFastType.Api/appsettings.Production.json diff --git a/PoFastType.Api/appsettings.json b/src/PoFastType.Api/appsettings.json similarity index 100% rename from PoFastType.Api/appsettings.json rename to src/PoFastType.Api/appsettings.json diff --git a/PoFastType.Api/log.txt b/src/PoFastType.Api/log.txt similarity index 100% rename from PoFastType.Api/log.txt rename to src/PoFastType.Api/log.txt diff --git a/PoFastType.Api/web.config b/src/PoFastType.Api/web.config similarity index 100% rename from PoFastType.Api/web.config rename to src/PoFastType.Api/web.config diff --git a/PoFastType.Client/App.razor b/src/PoFastType.Client/App.razor similarity index 100% rename from PoFastType.Client/App.razor rename to src/PoFastType.Client/App.razor diff --git a/PoFastType.Client/Components/ErrorBoundary.razor b/src/PoFastType.Client/Components/ErrorBoundary.razor similarity index 100% rename from PoFastType.Client/Components/ErrorBoundary.razor rename to src/PoFastType.Client/Components/ErrorBoundary.razor diff --git a/PoFastType.Client/Components/Navbar_New.razor b/src/PoFastType.Client/Components/Navbar_New.razor similarity index 100% rename from PoFastType.Client/Components/Navbar_New.razor rename to src/PoFastType.Client/Components/Navbar_New.razor diff --git a/PoFastType.Client/Layout/MainLayout.razor b/src/PoFastType.Client/Layout/MainLayout.razor similarity index 100% rename from PoFastType.Client/Layout/MainLayout.razor rename to src/PoFastType.Client/Layout/MainLayout.razor diff --git a/PoFastType.Client/Pages/Diag.razor b/src/PoFastType.Client/Pages/Diag.razor similarity index 100% rename from PoFastType.Client/Pages/Diag.razor rename to src/PoFastType.Client/Pages/Diag.razor diff --git a/PoFastType.Client/Pages/Home.razor b/src/PoFastType.Client/Pages/Home.razor similarity index 100% rename from PoFastType.Client/Pages/Home.razor rename to src/PoFastType.Client/Pages/Home.razor diff --git a/PoFastType.Client/Pages/Leaderboard.razor b/src/PoFastType.Client/Pages/Leaderboard.razor similarity index 100% rename from PoFastType.Client/Pages/Leaderboard.razor rename to src/PoFastType.Client/Pages/Leaderboard.razor diff --git a/PoFastType.Client/Pages/UserStats.razor b/src/PoFastType.Client/Pages/UserStats.razor similarity index 100% rename from PoFastType.Client/Pages/UserStats.razor rename to src/PoFastType.Client/Pages/UserStats.razor diff --git a/PoFastType.Client/PoFastType.Client.csproj b/src/PoFastType.Client/PoFastType.Client.csproj similarity index 100% rename from PoFastType.Client/PoFastType.Client.csproj rename to src/PoFastType.Client/PoFastType.Client.csproj diff --git a/PoFastType.Client/Program.cs b/src/PoFastType.Client/Program.cs similarity index 100% rename from PoFastType.Client/Program.cs rename to src/PoFastType.Client/Program.cs diff --git a/PoFastType.Client/Properties/launchSettings.json b/src/PoFastType.Client/Properties/launchSettings.json similarity index 100% rename from PoFastType.Client/Properties/launchSettings.json rename to src/PoFastType.Client/Properties/launchSettings.json diff --git a/PoFastType.Client/README.md b/src/PoFastType.Client/README.md similarity index 100% rename from PoFastType.Client/README.md rename to src/PoFastType.Client/README.md diff --git a/PoFastType.Client/Services/GameStateService.cs b/src/PoFastType.Client/Services/GameStateService.cs similarity index 100% rename from PoFastType.Client/Services/GameStateService.cs rename to src/PoFastType.Client/Services/GameStateService.cs diff --git a/PoFastType.Client/Services/IUserService.cs b/src/PoFastType.Client/Services/IUserService.cs similarity index 100% rename from PoFastType.Client/Services/IUserService.cs rename to src/PoFastType.Client/Services/IUserService.cs diff --git a/PoFastType.Client/Services/UserService.cs b/src/PoFastType.Client/Services/UserService.cs similarity index 100% rename from PoFastType.Client/Services/UserService.cs rename to src/PoFastType.Client/Services/UserService.cs diff --git a/PoFastType.Client/_Imports.razor b/src/PoFastType.Client/_Imports.razor similarity index 100% rename from PoFastType.Client/_Imports.razor rename to src/PoFastType.Client/_Imports.razor diff --git a/PoFastType.Client/wwwroot/app.js b/src/PoFastType.Client/wwwroot/app.js similarity index 100% rename from PoFastType.Client/wwwroot/app.js rename to src/PoFastType.Client/wwwroot/app.js diff --git a/PoFastType.Client/wwwroot/appsettings.json b/src/PoFastType.Client/wwwroot/appsettings.json similarity index 100% rename from PoFastType.Client/wwwroot/appsettings.json rename to src/PoFastType.Client/wwwroot/appsettings.json diff --git a/PoFastType.Client/wwwroot/css/retro-arcade.css b/src/PoFastType.Client/wwwroot/css/retro-arcade.css similarity index 100% rename from PoFastType.Client/wwwroot/css/retro-arcade.css rename to src/PoFastType.Client/wwwroot/css/retro-arcade.css diff --git a/PoFastType.Client/wwwroot/favicon.png b/src/PoFastType.Client/wwwroot/favicon.png similarity index 100% rename from PoFastType.Client/wwwroot/favicon.png rename to src/PoFastType.Client/wwwroot/favicon.png diff --git a/PoFastType.Client/wwwroot/icon-192.png b/src/PoFastType.Client/wwwroot/icon-192.png similarity index 100% rename from PoFastType.Client/wwwroot/icon-192.png rename to src/PoFastType.Client/wwwroot/icon-192.png diff --git a/PoFastType.Client/wwwroot/index.html b/src/PoFastType.Client/wwwroot/index.html similarity index 100% rename from PoFastType.Client/wwwroot/index.html rename to src/PoFastType.Client/wwwroot/index.html diff --git a/PoFastType.Client/wwwroot/sample-data/weather.json b/src/PoFastType.Client/wwwroot/sample-data/weather.json similarity index 100% rename from PoFastType.Client/wwwroot/sample-data/weather.json rename to src/PoFastType.Client/wwwroot/sample-data/weather.json diff --git a/PoFastType.Shared/Models/GameResult.cs b/src/PoFastType.Shared/Models/GameResult.cs similarity index 100% rename from PoFastType.Shared/Models/GameResult.cs rename to src/PoFastType.Shared/Models/GameResult.cs diff --git a/PoFastType.Shared/Models/LeaderboardEntry.cs b/src/PoFastType.Shared/Models/LeaderboardEntry.cs similarity index 100% rename from PoFastType.Shared/Models/LeaderboardEntry.cs rename to src/PoFastType.Shared/Models/LeaderboardEntry.cs diff --git a/PoFastType.Shared/Models/UserGameResult.cs b/src/PoFastType.Shared/Models/UserGameResult.cs similarity index 100% rename from PoFastType.Shared/Models/UserGameResult.cs rename to src/PoFastType.Shared/Models/UserGameResult.cs diff --git a/PoFastType.Shared/Models/UserIdentity.cs b/src/PoFastType.Shared/Models/UserIdentity.cs similarity index 100% rename from PoFastType.Shared/Models/UserIdentity.cs rename to src/PoFastType.Shared/Models/UserIdentity.cs diff --git a/PoFastType.Shared/PoFastType.Shared.csproj b/src/PoFastType.Shared/PoFastType.Shared.csproj similarity index 100% rename from PoFastType.Shared/PoFastType.Shared.csproj rename to src/PoFastType.Shared/PoFastType.Shared.csproj diff --git a/PoFastType.Shared/README.md b/src/PoFastType.Shared/README.md similarity index 100% rename from PoFastType.Shared/README.md rename to src/PoFastType.Shared/README.md diff --git a/PoFastType.Tests/API/Controllers/DiagControllerTests.cs b/tests/PoFastType.Tests/API/Controllers/DiagControllerTests.cs similarity index 100% rename from PoFastType.Tests/API/Controllers/DiagControllerTests.cs rename to tests/PoFastType.Tests/API/Controllers/DiagControllerTests.cs diff --git a/PoFastType.Tests/API/Controllers/GameControllerTests.cs b/tests/PoFastType.Tests/API/Controllers/GameControllerTests.cs similarity index 100% rename from PoFastType.Tests/API/Controllers/GameControllerTests.cs rename to tests/PoFastType.Tests/API/Controllers/GameControllerTests.cs diff --git a/PoFastType.Tests/API/Controllers/ScoresControllerTests.cs b/tests/PoFastType.Tests/API/Controllers/ScoresControllerTests.cs similarity index 100% rename from PoFastType.Tests/API/Controllers/ScoresControllerTests.cs rename to tests/PoFastType.Tests/API/Controllers/ScoresControllerTests.cs diff --git a/PoFastType.Tests/E2E/HomePageE2ETests.cs b/tests/PoFastType.Tests/E2E/HomePageE2ETests.cs similarity index 100% rename from PoFastType.Tests/E2E/HomePageE2ETests.cs rename to tests/PoFastType.Tests/E2E/HomePageE2ETests.cs diff --git a/PoFastType.Tests/E2E/LeaderboardPageE2ETests.cs b/tests/PoFastType.Tests/E2E/LeaderboardPageE2ETests.cs similarity index 100% rename from PoFastType.Tests/E2E/LeaderboardPageE2ETests.cs rename to tests/PoFastType.Tests/E2E/LeaderboardPageE2ETests.cs diff --git a/PoFastType.Tests/E2E/ResponsiveDesignE2ETests.cs b/tests/PoFastType.Tests/E2E/ResponsiveDesignE2ETests.cs similarity index 100% rename from PoFastType.Tests/E2E/ResponsiveDesignE2ETests.cs rename to tests/PoFastType.Tests/E2E/ResponsiveDesignE2ETests.cs diff --git a/PoFastType.Tests/E2E/UserStatsPageE2ETests.cs b/tests/PoFastType.Tests/E2E/UserStatsPageE2ETests.cs similarity index 100% rename from PoFastType.Tests/E2E/UserStatsPageE2ETests.cs rename to tests/PoFastType.Tests/E2E/UserStatsPageE2ETests.cs diff --git a/PoFastType.Tests/Integration/AzureTableStorageIntegrationTests.cs b/tests/PoFastType.Tests/Integration/AzureTableStorageIntegrationTests.cs similarity index 100% rename from PoFastType.Tests/Integration/AzureTableStorageIntegrationTests.cs rename to tests/PoFastType.Tests/Integration/AzureTableStorageIntegrationTests.cs diff --git a/PoFastType.Tests/Integration/Services/GameServiceTests.cs b/tests/PoFastType.Tests/Integration/Services/GameServiceTests.cs similarity index 100% rename from PoFastType.Tests/Integration/Services/GameServiceTests.cs rename to tests/PoFastType.Tests/Integration/Services/GameServiceTests.cs diff --git a/PoFastType.Tests/PoFastType.Tests.csproj b/tests/PoFastType.Tests/PoFastType.Tests.csproj similarity index 89% rename from PoFastType.Tests/PoFastType.Tests.csproj rename to tests/PoFastType.Tests/PoFastType.Tests.csproj index b069fae..83c556a 100644 --- a/PoFastType.Tests/PoFastType.Tests.csproj +++ b/tests/PoFastType.Tests/PoFastType.Tests.csproj @@ -35,8 +35,8 @@
- - + +
diff --git a/PoFastType.Tests/README.md b/tests/PoFastType.Tests/README.md similarity index 100% rename from PoFastType.Tests/README.md rename to tests/PoFastType.Tests/README.md diff --git a/PoFastType.Tests/System/TypingGameSystemTests.cs b/tests/PoFastType.Tests/System/TypingGameSystemTests.cs similarity index 100% rename from PoFastType.Tests/System/TypingGameSystemTests.cs rename to tests/PoFastType.Tests/System/TypingGameSystemTests.cs diff --git a/PoFastType.Tests/TestHelpers/CustomWebApplicationFactory.cs b/tests/PoFastType.Tests/TestHelpers/CustomWebApplicationFactory.cs similarity index 100% rename from PoFastType.Tests/TestHelpers/CustomWebApplicationFactory.cs rename to tests/PoFastType.Tests/TestHelpers/CustomWebApplicationFactory.cs diff --git a/PoFastType.Tests/Unit/Repositories/AzureTableGameResultRepositoryTests.cs b/tests/PoFastType.Tests/Unit/Repositories/AzureTableGameResultRepositoryTests.cs similarity index 100% rename from PoFastType.Tests/Unit/Repositories/AzureTableGameResultRepositoryTests.cs rename to tests/PoFastType.Tests/Unit/Repositories/AzureTableGameResultRepositoryTests.cs diff --git a/PoFastType.Tests/Unit/Services/HardcodedTextStrategyTests.cs b/tests/PoFastType.Tests/Unit/Services/HardcodedTextStrategyTests.cs similarity index 100% rename from PoFastType.Tests/Unit/Services/HardcodedTextStrategyTests.cs rename to tests/PoFastType.Tests/Unit/Services/HardcodedTextStrategyTests.cs diff --git a/PoFastType.Tests/Unit/Services/TextGenerationServiceTests.cs b/tests/PoFastType.Tests/Unit/Services/TextGenerationServiceTests.cs similarity index 100% rename from PoFastType.Tests/Unit/Services/TextGenerationServiceTests.cs rename to tests/PoFastType.Tests/Unit/Services/TextGenerationServiceTests.cs From a59be6655a22950e918535f8258aaa4f1de17600 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:27:36 +0000 Subject: [PATCH 4/6] Add Serilog config, Azure Key Vault, and OpenTelemetry support Co-authored-by: punkouter26 <121304072+punkouter26@users.noreply.github.com> --- Directory.Packages.props | 3 + src/PoFastType.Api/PoFastType.Api.csproj | 3 + src/PoFastType.Api/Program.cs | 73 ++++++++++++------- .../appsettings.Development.json | 25 +++---- .../appsettings.Production.json | 22 +++++- src/PoFastType.Api/appsettings.json | 44 +++++++++-- 6 files changed, 122 insertions(+), 48 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index cbe75a9..7adc001 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,12 +9,15 @@ + + + diff --git a/src/PoFastType.Api/PoFastType.Api.csproj b/src/PoFastType.Api/PoFastType.Api.csproj index dcd2524..f1805ee 100644 --- a/src/PoFastType.Api/PoFastType.Api.csproj +++ b/src/PoFastType.Api/PoFastType.Api.csproj @@ -11,6 +11,7 @@ + @@ -23,6 +24,8 @@ + + diff --git a/src/PoFastType.Api/Program.cs b/src/PoFastType.Api/Program.cs index 0734f74..e9a1c03 100644 --- a/src/PoFastType.Api/Program.cs +++ b/src/PoFastType.Api/Program.cs @@ -6,32 +6,38 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Diagnostics.HealthChecks; using Serilog; -using Serilog.Formatting.Compact; -using System.IO; +using Azure.Identity; +using Azure.Security.KeyVault.Secrets; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using OpenTelemetry.Resources; var builder = WebApplication.CreateBuilder(args); -// Ensure DEBUG directory exists -var debugPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "DEBUG"); -Directory.CreateDirectory(debugPath); +// Configure Azure Key Vault for Production only +if (builder.Environment.IsProduction()) +{ + var keyVaultUri = builder.Configuration["AzureKeyVault:VaultUri"]; + if (!string.IsNullOrEmpty(keyVaultUri)) + { + var secretClient = new SecretClient( + new Uri(keyVaultUri), + new DefaultAzureCredential()); + + builder.Configuration.AddAzureKeyVault( + new Uri(keyVaultUri), + new DefaultAzureCredential()); + } +} -// Configure Serilog with structured JSON logging and overwrite behavior +// Configure Serilog from appsettings.json Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo.Console(new CompactJsonFormatter()) - .WriteTo.File( - new CompactJsonFormatter(), - Path.Combine(debugPath, "log.txt"), - rollingInterval: RollingInterval.Infinite, - rollOnFileSizeLimit: false, - shared: false, - buffered: false, - flushToDiskInterval: TimeSpan.FromSeconds(1)) + .ReadFrom.Configuration(builder.Configuration) + .Enrich.FromLogContext() .Enrich.WithProperty("Application", "PoFastType") .Enrich.WithProperty("Environment", builder.Environment.EnvironmentName) .CreateLogger(); -// Log application startup Log.Information("PoFastType application starting up at {Timestamp}", DateTime.UtcNow); builder.Host.UseSerilog(); @@ -39,13 +45,27 @@ // Add Application Insights builder.Services.AddApplicationInsightsTelemetry(); -// Configure logging to write to console and file +// Add OpenTelemetry +builder.Services.AddOpenTelemetry() + .ConfigureResource(resource => resource + .AddService("PoFastType") + .AddAttributes(new Dictionary + { + ["deployment.environment"] = builder.Environment.EnvironmentName + })) + .WithMetrics(metrics => metrics + .AddAspNetCoreInstrumentation() + .AddRuntimeInstrumentation() + .AddMeter("PoFastType.Metrics")) + .WithTracing(tracing => tracing + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation()); + +// Configure logging builder.Logging.ClearProviders(); builder.Logging.AddSerilog(); -builder.Logging.SetMinimumLevel(LogLevel.Debug); -// Add services to the container. -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +// Add services to the container builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -70,8 +90,10 @@ } else { - // In production, be more restrictive - policy.WithOrigins("https://pofasttype.azurewebsites.net") + // In production, use configured origins + var allowedOrigins = builder.Configuration.GetSection("CORS:AllowedOrigins").Get() + ?? new[] { "https://pofasttype.azurewebsites.net" }; + policy.WithOrigins(allowedOrigins) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); @@ -101,10 +123,10 @@ // Add global exception handling middleware first app.UseMiddleware(); -// Configure the HTTP request pipeline. +// Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { - app.UseDeveloperExceptionPage(); // Add this for detailed error pages in development + app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(); app.UseWebAssemblyDebugging(); @@ -150,7 +172,6 @@ // Fallback to serve the Blazor WebAssembly app for non-API routes app.MapFallbackToFile("index.html"); -// Log application ready state Log.Information("PoFastType application configured and ready to serve requests"); try diff --git a/src/PoFastType.Api/appsettings.Development.json b/src/PoFastType.Api/appsettings.Development.json index 26a4e9c..1f0a572 100644 --- a/src/PoFastType.Api/appsettings.Development.json +++ b/src/PoFastType.Api/appsettings.Development.json @@ -1,4 +1,15 @@ { + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "Microsoft.AspNetCore": "Information", + "PoFastType.Api.Middleware": "Debug", + "PoFastType.Api.Controllers": "Debug" + } + } + }, "Logging": { "LogLevel": { "Default": "Debug", @@ -13,13 +24,6 @@ }, "Console": { "IncludeScopes": true - }, - "File": { - "Path": "log.txt", - "Append": false, - "MinLevel": "Debug", - "FileSizeLimitBytes": 10485760, - "MaxRollingFiles": 1 } }, "DebugSettings": { @@ -37,12 +41,7 @@ "TableName": "PoFastTypeGameResults" }, "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=your-ai-key;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=your-app-id" - }, - "AzureOpenAI": { - "Endpoint": "https://posharedopenai.openai.azure.com/", - "ApiKey": "your-openai-key", - "DeploymentName": "gpt-4" + "ConnectionString": "" }, "AllowedHosts": "*" } diff --git a/src/PoFastType.Api/appsettings.Production.json b/src/PoFastType.Api/appsettings.Production.json index a769b52..8500485 100644 --- a/src/PoFastType.Api/appsettings.Production.json +++ b/src/PoFastType.Api/appsettings.Production.json @@ -1,4 +1,20 @@ { + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning", + "System.Net.Http.HttpClient": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console" + } + ] + }, "Logging": { "LogLevel": { "Default": "Information", @@ -9,11 +25,11 @@ }, "AllowedHosts": "*", "AzureTableStorage": { - "ConnectionString": "", "TableName": "PoFastTypeGameResults" }, "ApplicationInsights": { - "ConnectionString": "" + "EnableAdaptiveSampling": true, + "EnablePerformanceCounterCollectionModule": true }, "HealthChecks": { "TimeoutSeconds": 30, @@ -21,7 +37,7 @@ }, "CORS": { "AllowedOrigins": [ - "https://pofasttype.azurecontainerapps.io" + "https://pofasttype.azurewebsites.net" ] } } diff --git a/src/PoFastType.Api/appsettings.json b/src/PoFastType.Api/appsettings.json index f1a0a2f..b0a74a3 100644 --- a/src/PoFastType.Api/appsettings.json +++ b/src/PoFastType.Api/appsettings.json @@ -1,4 +1,38 @@ { + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.AspNetCore": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" + } + }, + { + "Name": "File", + "Args": { + "path": "../DEBUG/log.txt", + "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact", + "rollingInterval": "Infinite", + "rollOnFileSizeLimit": false, + "shared": false, + "flushToDiskInterval": "00:00:01" + } + } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "Properties": { + "Application": "PoFastType" + } + }, "Logging": { "LogLevel": { "Default": "Information", @@ -16,15 +50,13 @@ "MinimumLogLevel": "Debug" }, "AzureTableStorage": { - "ConnectionString": "DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net;AccountName=posharedtablestorage;AccountKey=LPMW8tZgPJJnYLhCRME+KW4fViv3rJZA+XrycWCjO89yFOMaE2Qi7m3IAkb5dtdD8cR6SFk478b++ASt5ZuqfA==;BlobEndpoint=https://posharedtablestorage.blob.core.windows.net/;FileEndpoint=https://posharedtablestorage.file.core.windows.net/;QueueEndpoint=https://posharedtablestorage.queue.core.windows.net/;TableEndpoint=https://posharedtablestorage.table.core.windows.net/", + "ConnectionString": "UseDevelopmentStorage=true", "TableName": "PoFastTypeGameResults" }, "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=7027f796-a9be-4543-8ffd-bf655ae47a44;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=7530837a-f820-4dd4-8593-daade7bfb589" + "ConnectionString": "" }, - "AzureOpenAI": { - "Endpoint": "https://posharedopenai.openai.azure.com/", - "ApiKey": "your-openai-key", - "DeploymentName": "gpt-4" + "AzureKeyVault": { + "VaultUri": "https://pofasttype-kv.vault.azure.net/" } } From fdaf5546e8ba88c71be778b2ae59898209289fb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:29:32 +0000 Subject: [PATCH 5/6] Update Bicep infrastructure: Key Vault, budget alerts, Snapshot Debugger, .NET 10 Co-authored-by: punkouter26 <121304072+punkouter26@users.noreply.github.com> --- infra/budget.bicep | 60 ++++++++++++++++++++++++++++++++++ infra/main.bicep | 15 +++++++++ infra/resources.bicep | 76 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 infra/budget.bicep diff --git a/infra/budget.bicep b/infra/budget.bicep new file mode 100644 index 0000000..371e405 --- /dev/null +++ b/infra/budget.bicep @@ -0,0 +1,60 @@ +targetScope = 'resourceGroup' + +@description('Name of the budget') +param budgetName string = 'PoFastType-MonthlyBudget' + +@description('The total amount of cost or usage to track with the budget') +param amount int = 5 + +@description('The time covered by a budget. Tracking of the amount will be reset based on the time grain.') +@allowed([ + 'Monthly' + 'Quarterly' + 'Annually' + 'BillingMonth' + 'BillingQuarter' + 'BillingAnnual' +]) +param timeGrain string = 'Monthly' + +@description('The start date for the budget') +param startDate string = utcNow('yyyy-MM-01') + +@description('The end date for the budget (5 years from now)') +param endDate string = dateTimeAdd(utcNow(), 'P5Y', 'yyyy-MM-dd') + +@description('Action Group Resource ID for budget alerts') +param actionGroupId string + +@description('Threshold percentage for alert (default: 80%)') +param thresholdPercentage int = 80 + +resource budget 'Microsoft.Consumption/budgets@2023-11-01' = { + name: budgetName + properties: { + timePeriod: { + startDate: startDate + endDate: endDate + } + timeGrain: timeGrain + amount: amount + category: 'Cost' + notifications: { + 'Actual_GreaterThan_${thresholdPercentage}_Percent': { + enabled: true + operator: 'GreaterThan' + threshold: thresholdPercentage + contactEmails: [ + 'punkouter26@gmail.com' + ] + contactGroups: [ + actionGroupId + ] + thresholdType: 'Actual' + } + } + } +} + +output budgetId string = budget.id +output budgetName string = budget.name diff --git a/infra/main.bicep b/infra/main.bicep index 96b92e8..cbe78a7 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -39,8 +39,23 @@ module resources 'resources.bicep' = { } } +// Deploy budget with alert +module budget 'budget.bicep' = { + scope: rg + name: 'budget' + params: { + budgetName: 'PoFastType-MonthlyBudget' + amount: 5 + actionGroupId: resources.outputs.BUDGET_ACTION_GROUP_ID + } + dependsOn: [ + resources + ] +} + output AZURE_LOCATION string = location output AZURE_TENANT_ID string = tenant().tenantId output WEBSITE_URL string = resources.outputs.WEBSITE_URL output API_BASE_URL string = resources.outputs.API_BASE_URL output RESOURCE_GROUP_ID string = rg.id +output BUDGET_ID string = budget.outputs.budgetId diff --git a/infra/resources.bicep b/infra/resources.bicep index 994923d..4877f2a 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -99,6 +99,45 @@ resource gameResultsTable 'Microsoft.Storage/storageAccounts/tableServices/table name: 'PoFastTypeGameResults' } +// Create Key Vault for secrets +resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = { + name: 'pofasttype-kv' + location: location + tags: tags + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: subscription().tenantId + enableRbacAuthorization: true + enableSoftDelete: true + softDeleteRetentionInDays: 7 + enabledForDeployment: false + enabledForDiskEncryption: false + enabledForTemplateDeployment: false + publicNetworkAccess: 'Enabled' + } +} + +// Create Action Group for budget alerts +resource budgetAlertActionGroup 'Microsoft.Insights/actionGroups@2023-01-01' = { + name: 'PoFastType-budget-alerts' + location: 'global' + tags: tags + properties: { + groupShortName: 'BudgetAlert' + enabled: true + emailReceivers: [ + { + name: 'OwnerEmail' + emailAddress: 'punkouter26@gmail.com' + useCommonAlertSchema: true + } + ] + } +} + // Create a user-assigned managed identity resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: 'PoFastType-identity' @@ -116,7 +155,7 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' = { serverFarmId: existingAppServicePlan.id httpsOnly: true siteConfig: { - netFrameworkVersion: 'v9.0' + netFrameworkVersion: 'v10.0' cors: { allowedOrigins: ['*'] supportCredentials: false @@ -138,6 +177,36 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' = { name: 'AzureTableStorage__TableName' value: 'PoFastTypeGameResults' } + { + name: 'AzureKeyVault__VaultUri' + value: keyVault.properties.vaultUri + } + // Enable Application Insights Snapshot Debugger + { + name: 'APPINSIGHTS_SNAPSHOTFEATURE_VERSION' + value: '1.0.0' + } + { + name: 'SnapshotDebugger__IsEnabled' + value: 'true' + } + { + name: 'SnapshotDebugger__UploadAllSnapshots' + value: 'true' + } + // Enable Application Insights Profiler + { + name: 'APPINSIGHTS_PROFILERFEATURE_VERSION' + value: '1.0.0' + } + { + name: 'ApplicationInsightsProfiler__IsEnabled' + value: 'true' + } + { + name: 'DiagnosticServices__EnableProfiler' + value: 'true' + } ] } } @@ -154,7 +223,7 @@ resource webAppConfig 'Microsoft.Web/sites/config@2022-03-01' = { parent: webApp name: 'web' properties: { - netFrameworkVersion: 'v9.0' + netFrameworkVersion: 'v10.0' scmType: 'GitHubAction' use32BitWorkerProcess: true webSocketsEnabled: false @@ -213,3 +282,6 @@ output AZURE_WEB_APP_NAME string = webApp.name output AZURE_STORAGE_ACCOUNT_NAME string = storageAccount.name output AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING string = appInsights.properties.ConnectionString output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = logAnalyticsWorkspace.id +output AZURE_KEY_VAULT_NAME string = keyVault.name +output AZURE_KEY_VAULT_URI string = keyVault.properties.vaultUri +output BUDGET_ACTION_GROUP_ID string = budgetAlertActionGroup.id From a2454fd9475dd1f8388185f1741c30f7f0263314 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:32:24 +0000 Subject: [PATCH 6/6] Fix code formatting and add architecture modernization summary Co-authored-by: punkouter26 <121304072+punkouter26@users.noreply.github.com> --- docs/ARCHITECTURE_MODERNIZATION.md | 238 ++++++++++++++++++++ src/PoFastType.Client/Pages/UserStats.razor | 3 +- src/PoFastType.Client/wwwroot/index.html | 7 +- 3 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 docs/ARCHITECTURE_MODERNIZATION.md diff --git a/docs/ARCHITECTURE_MODERNIZATION.md b/docs/ARCHITECTURE_MODERNIZATION.md new file mode 100644 index 0000000..4bde9ea --- /dev/null +++ b/docs/ARCHITECTURE_MODERNIZATION.md @@ -0,0 +1,238 @@ +# Architecture Modernization Summary + +## Overview +This document summarizes the comprehensive architecture updates made to the PoFastType application to meet enterprise-grade .NET standards. + +## Completed Updates + +### 1. Foundation (.NET 10 Migration) +โœ… **Migrated from .NET 9 to .NET 10** +- Created `global.json` to lock SDK version at 10.0.100 +- Updated all `.csproj` files to target `net10.0` +- Updated Bicep infrastructure to deploy with .NET 10 + +โœ… **Centralized Package Management** +- Created `Directory.Packages.props` at repository root +- Removed version numbers from individual `.csproj` files +- Includes all major packages: Azure SDKs, OpenTelemetry, Serilog, MediatR, FluentValidation, bUnit, Playwright + +โœ… **VS Code Integration** +- Created `.vscode/launch.json` with `serverReadyAction` for F5 debugging +- Created `.vscode/tasks.json` for build automation +- Configured one-step debug launch for API and browser + +### 2. Repository Structure +โœ… **Reorganized to Standard Folder Structure** +``` +/src - All source code projects +/tests - All test projects +/docs - Documentation, diagrams, coverage reports, KQL queries +/infra - Bicep infrastructure templates +/scripts - Helper scripts (Azurite, coverage) +``` + +โœ… **Documentation Organization** +- Moved all diagrams to `/docs/Diagrams/` +- Moved PRD.md to `/docs/` +- Created `/docs/kql/` with 6 essential monitoring queries +- Created `/docs/README.md` as documentation index +- Updated all path references in README.md + +### 3. Logging & Observability +โœ… **Serilog Configuration** +- Configured Serilog to read from `appsettings.json` +- Separate configurations for Development and Production +- Structured JSON logging with compact formatter +- File and console sinks configured + +โœ… **OpenTelemetry Integration** +- Added OpenTelemetry with custom metrics support +- Configured for ASP.NET Core and HTTP client instrumentation +- Runtime metrics collection enabled +- Custom meter "PoFastType.Metrics" for business metrics + +โœ… **Application Insights** +- Enabled Snapshot Debugger for production debugging +- Enabled Profiler for performance analysis +- Configured for Production environment + +### 4. Azure Infrastructure (Bicep) +โœ… **Complete Infrastructure as Code** +- Log Analytics Workspace for centralized logging +- Application Insights for monitoring and diagnostics +- Azure Storage Account with Table Storage +- Azure Key Vault for secrets management +- User-Assigned Managed Identity +- Action Group for budget alerts + +โœ… **Cost Management** +- Created $5 monthly budget +- Configured 80% threshold alert +- Email notifications to punkouter26@gmail.com +- Deployed via separate `budget.bicep` module + +โœ… **Security** +- Azure Key Vault integration (Production only) +- TLS 1.2 minimum enforced +- HTTPS only enabled +- Managed identity for secure access +- Storage encryption enabled + +### 5. Development Environment +โœ… **Local Development** +- Azurite configured as default storage (Development) +- Production uses Azure Table Storage +- Scripts created for starting Azurite (PowerShell & Bash) + +โœ… **Helper Scripts** +- `scripts/start-azurite.ps1` & `.sh` - Start Azure Storage Emulator +- `scripts/run-coverage.ps1` & `.sh` - Run code coverage with reportgenerator + +### 6. Monitoring & Diagnostics +โœ… **KQL Query Library** +Created 6 essential queries in `/docs/kql/`: +1. `app-performance.kql` - Request metrics and performance +2. `user-activity.kql` - User engagement tracking +3. `top-scores.kql` - Leaderboard and performance metrics +4. `error-analysis.kql` - Exception and failure tracking +5. `endpoint-performance.kql` - API endpoint performance +6. `storage-health.kql` - Azure Storage dependency health + +### 7. Testing Infrastructure +โœ… **Test Framework Updates** +- Added bUnit for Blazor component testing +- Added Playwright for E2E testing +- Coverage scripts for reportgenerator integration +- All test packages centrally managed + +## Pending Items + +### High Priority +- [ ] Refactor API to Vertical Slice Architecture with `/Features` folder +- [ ] Convert API from Controllers to Minimal APIs +- [ ] Ensure PoFastType.Shared contains only DTOs and validation logic +- [ ] Configure dotnet-coverage with 80% threshold +- [ ] Update API error handling to RFC 7807 Problem Details + +### Medium Priority +- [ ] Configure GitHub Actions with OIDC/Federated Credentials +- [ ] Ensure all test methods follow naming convention +- [ ] Set up automated coverage report generation in CI/CD + +## Benefits Achieved + +### Performance +- OpenTelemetry metrics for performance monitoring +- Application Insights Profiler for bottleneck identification +- Structured logging for efficient log querying + +### Reliability +- Health checks for dependency monitoring +- Snapshot Debugger for production issue diagnosis +- Budget alerts for cost control + +### Developer Experience +- One-step F5 debugging +- Centralized package management +- Helper scripts for common tasks +- Comprehensive KQL query library + +### Security +- Azure Key Vault for secrets (Production) +- Managed identities for secure access +- TLS 1.2+ enforcement +- Storage encryption enabled + +### Maintainability +- Clean folder structure +- Comprehensive documentation +- Infrastructure as Code +- Centralized configuration + +## Architecture Patterns + +### Applied Patterns +- **Dependency Injection** - IoC container for all services +- **Single Responsibility** - Each service has one clear purpose +- **Dependency Inversion** - Depend on abstractions (interfaces) +- **Configuration-based** - Serilog, CORS, health checks from config + +### Planned Patterns +- **Vertical Slice Architecture** - Feature-based organization +- **CQRS with MediatR** - Command/Query separation +- **FluentValidation** - Declarative validation rules +- **Problem Details (RFC 7807)** - Standardized error responses + +## Technology Stack + +### Frontend +- Blazor WebAssembly +- Radzen UI Components +- Bootstrap 5.3 +- ChartJS + +### Backend +- .NET 10 +- ASP.NET Core +- MediatR (for CQRS when implemented) +- FluentValidation (for validation when implemented) + +### Infrastructure +- Azure App Service +- Azure Table Storage +- Azure Key Vault +- Application Insights +- Log Analytics + +### DevOps +- GitHub Actions +- Azure Developer CLI (azd) +- Bicep (Infrastructure as Code) +- Azurite (local development) + +## Migration Notes + +### Breaking Changes +- All projects now target .NET 10 (was .NET 9) +- Folder structure changed - all imports and paths updated +- Serilog now reads from configuration (was code-based) + +### Non-Breaking Changes +- Added centralized package management +- Added OpenTelemetry (additive) +- Added Azure Key Vault support (Production only) +- Added budget monitoring (Azure only) + +## Next Steps + +1. **Implement Vertical Slice Architecture** + - Create `/src/PoFastType.Api/Features/` folder + - Organize endpoints by feature + - Implement CQRS with MediatR + +2. **Convert to Minimal APIs** + - Replace Controllers with Minimal API endpoints + - Group endpoints by feature + - Add FluentValidation for request validation + +3. **Enhance Error Handling** + - Implement RFC 7807 Problem Details globally + - Add custom problem detail types for domain errors + - Ensure all errors return structured responses + +4. **Configure CI/CD** + - Set up OIDC authentication for GitHub Actions + - Implement automated testing with coverage reports + - Deploy to Azure on merge to main + +## Conclusion + +This modernization effort has successfully: +- Upgraded the application to .NET 10 +- Implemented enterprise-grade observability +- Established cost controls and monitoring +- Improved developer experience +- Enhanced security posture +- Prepared foundation for architectural improvements + +The application is now positioned for continued evolution with a solid foundation of modern .NET practices and Azure cloud-native patterns. diff --git a/src/PoFastType.Client/Pages/UserStats.razor b/src/PoFastType.Client/Pages/UserStats.razor index c760e4e..207dfc3 100644 --- a/src/PoFastType.Client/Pages/UserStats.razor +++ b/src/PoFastType.Client/Pages/UserStats.razor @@ -169,7 +169,8 @@ private async void OnUserStatsRefreshRequested() { - await LoadUserStats(); StateHasChanged(); + await LoadUserStats(); + StateHasChanged(); } private async Task LoadUserStats() diff --git a/src/PoFastType.Client/wwwroot/index.html b/src/PoFastType.Client/wwwroot/index.html index 25d3f14..9d0b1db 100644 --- a/src/PoFastType.Client/wwwroot/index.html +++ b/src/PoFastType.Client/wwwroot/index.html @@ -3,7 +3,8 @@ - PoFastType + + PoFastType @@ -26,7 +27,9 @@ An unhandled error has occurred. Reload ๐Ÿ—™ - + + +