Skip to content

Apply NFR improvements: security, reliability, observability, performance, testability - #13

Open
vanespasov wants to merge 14 commits into
masterfrom
feature/nfr-improvements
Open

Apply NFR improvements: security, reliability, observability, performance, testability#13
vanespasov wants to merge 14 commits into
masterfrom
feature/nfr-improvements

Conversation

@vanespasov

Copy link
Copy Markdown
Owner

Security (P0)

  • Remove hardcoded Bing Maps key; bind via IOptions and User Secrets
  • Remove unused key field from WASM client bundle
  • Restrict AllowedHosts to localhost in production config
  • Add fixed-window rate limiter (60 req/min) on geocode endpoint
  • Validate location length in controller (BadRequest >100 chars)

Reliability (P1)

  • Replace blocking .Result Bing call with proper async/await + 10s WaitAsync timeout
  • Introduce IGeocodingService / BingGeocodingService; controller is now a thin delegate
  • Replace fragile colon-delimited string response with typed GeocodeResult JSON DTO
  • Client: Task.WhenAll for parallel validation, loading spinner, user-visible error alert
  • Wire Countdown.TimerOut to auto-submit the active game on expiry
  • Null-safe Game.TotalPoints using ?. on every location

Observability (P2)

  • Call app.MapDefaultEndpoints() — /health and /alive now respond
  • Structured ILogger calls in BingGeocodingService (outcome, cache hit, latency, errors)
  • Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.9.0 (NU1902) to 1.18.0
  • Add GitHub Actions CI workflow (build, unit tests, integration tests, vuln scan)

Performance (P3)

  • Parallelize five per-answer HTTP calls with Task.WhenAll
  • IMemoryCache on BingGeocodingService keyed by (answer, locationType), TTL 24h

Testability and maintainability (P4)

  • Extract ScoringRules (constants) and BadgeCalculator to FastGeography.Shared
  • Extract IGeocodingService port and BingGeocodingService adapter
  • Unit tests: 32 cases for SetCssClass, TotalPoints, ScoringRules, all badge tiers
  • Integration tests converted to Reqnroll/Gherkin BDD (8 scenarios, fake geocoder)
  • Fix E2E test selectors; mark Playwright tests as Skip until dev server available
  • Delete dead template code: HomeController, Counter.razor, SurveyPrompt.razor
  • Restore @Body routing in MainLayout; Index.razor redirects to /fastgeography
  • Remove duplicate Client project from AppHost (Client is hosted by Server)

UX and Accessibility (P5)

  • Drop user-scalable=no from viewport meta
  • Add aria-label on game inputs, role=timer on countdown, aria-live on achievement banner
  • Add IsChecking parameter to GameRow to disable fields during validation
  • Add SVG assets: visit.svg and badge-0..9.svg (were missing, causing broken images)

vanespasov-rldatix and others added 14 commits August 27, 2026 23:21
…ance, testability

Security (P0)
- Remove hardcoded Bing Maps key; bind via IOptions<BingMapsOptions> and User Secrets
- Remove unused key field from WASM client bundle
- Restrict AllowedHosts to localhost in production config
- Add fixed-window rate limiter (60 req/min) on geocode endpoint
- Validate location length in controller (BadRequest >100 chars)

Reliability (P1)
- Replace blocking .Result Bing call with proper async/await + 10s WaitAsync timeout
- Introduce IGeocodingService / BingGeocodingService; controller is now a thin delegate
- Replace fragile colon-delimited string response with typed GeocodeResult JSON DTO
- Client: Task.WhenAll for parallel validation, loading spinner, user-visible error alert
- Wire Countdown.TimerOut to auto-submit the active game on expiry
- Null-safe Game.TotalPoints using ?. on every location

Observability (P2)
- Call app.MapDefaultEndpoints() — /health and /alive now respond
- Structured ILogger calls in BingGeocodingService (outcome, cache hit, latency, errors)
- Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.9.0 (NU1902) to 1.18.0
- Add GitHub Actions CI workflow (build, unit tests, integration tests, vuln scan)

Performance (P3)
- Parallelize five per-answer HTTP calls with Task.WhenAll
- IMemoryCache on BingGeocodingService keyed by (answer, locationType), TTL 24h

Testability and maintainability (P4)
- Extract ScoringRules (constants) and BadgeCalculator to FastGeography.Shared
- Extract IGeocodingService port and BingGeocodingService adapter
- Unit tests: 32 cases for SetCssClass, TotalPoints, ScoringRules, all badge tiers
- Integration tests converted to Reqnroll/Gherkin BDD (8 scenarios, fake geocoder)
- Fix E2E test selectors; mark Playwright tests as Skip until dev server available
- Delete dead template code: HomeController, Counter.razor, SurveyPrompt.razor
- Restore @Body routing in MainLayout; Index.razor redirects to /fastgeography
- Remove duplicate Client project from AppHost (Client is hosted by Server)

UX and Accessibility (P5)
- Drop user-scalable=no from viewport meta
- Add aria-label on game inputs, role=timer on countdown, aria-live on achievement banner
- Add IsChecking parameter to GameRow to disable fields during validation
- Add SVG assets: visit.svg and badge-0..9.svg (were missing, causing broken images)

Co-authored-by: Cursor <cursoragent@cursor.com>
Orchestrate only FastGeography.Server so the dashboard opens a single origin that serves both the WASM UI and the API. Pass BingMaps__ApiKey from an AppHost secret parameter instead of appsettings.

Co-authored-by: Cursor <cursoragent@cursor.com>
…and personal history table

- Persist all five answer fields (CityAnswer…MountainAnswer) in RoundSubmission for
  multiplayer rounds, matching the existing solo behaviour (no migration needed)
- Track set progress on GameRoom: RoundsCompletedInSet, SetComplete, PlayerSetHistory;
  StartRound is blocked once 5 rounds are complete
- Add StartNewSet hub method (host-only): resets counter and per-player history and
  broadcasts NewSetStarted so all clients clear their personal tables
- Add LeaveRoom hub method: removes the player from the SignalR group, transfers host to
  the next remaining player, and broadcasts PlayerLeft / HostChanged
- Fix OnDisconnectedAsync to broadcast PlayerLeft and trigger host transfer on disconnect
- Extend DTOs: RoomStateDto and RoundStartedMessage carry RoundNumber; RoundResultsMessage
  carries RoundsCompletedInSet and SetComplete; new CompletedRoundRow carries per-player
  answers for the personal history table
- Rewrite MultiplayerGame.razor with a casual-play-style stacked personal table (finished
  rows via GameRow + live editable row for the current round), Leave Room button in the
  header, Round N/5 counter, set-total points badge, and Play Another 5 Rounds CTA for
  the host after the set ends; all-player Round Results table is kept visible between rounds
- Add 5 new integration tests covering answer text in results, leave/notify, host transfer
  on leave, 6th-round rejection, and new-set then round-1 flow

Co-authored-by: Cursor <cursoragent@cursor.com>
Nominatim geocoding:
- Accept jsonv2 "category" field as an alias for "class" (FeatureClass computed property)
  so type matching works for both json and jsonv2 response formats
- Add addressType-based fallback matching for mountains and rivers so results that carry
  addressType but no class/category are still recognised
- Add "volcano" to mountain types (both type and addressType sets)
- Switch User-Agent registration from ParseAdd to TryAddWithoutValidation to avoid parse
  failures when the value contains URLs or semicolons; also add Accept: application/json
- Update default and development User-Agent to use the GitHub project URL
- Add integration tests for jsonv2 Category peak, volcano addressType, and river addressType

Leaderboard controller:
- Fix EF Core GroupBy translation error on the weekly filter: project only UserId and
  computed Points before ToListAsync, then aggregate in memory

Scoreboard UI:
- Wrap GetFromJsonAsync in try/catch; show an alert on failure instead of a blank page
- Add #blazor-error-ui.show { display: block } to app.css so the Blazor circuit error
  banner is visible when the framework sets the "show" class

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduces a curated static catalog of ~400 real toponyms (WellKnownToponyms)
covering every playable letter x category for English (A-Z) and Macedonian
(A-Sh) where a genuine place exists. Geographic gaps (e.g. no sovereign state
starting with X/W in EN, rare Cyrillic letters in MK) are documented in the
catalog header rather than invented.

Applied two ways:
- EF migration SeedWellKnownToponyms: INSERT ON CONFLICT DO NOTHING so
  existing player-verified rows are never overwritten; Down deletes Provider=Seed.
- ToponymSeeder: idempotent runtime seeder after MigrateAsync/EnsureCreatedAsync;
  no-op on Postgres after the migration runs.

Tests: coverage/uniqueness checks for the catalog, integration tests verifying
catalog hits (London/Lonon/Vardar) and miss for garbage names, idempotency,
and correct Provider tag. SpyGeocodingService extracted to a shared file.

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduces IStringLocalizer<UiStrings> across all client pages and
components so the sidebar language picker switches the full UI to
Macedonian instantly without a page reload.

- Added UiStrings.resx (English) and UiStrings.mk.resx (Macedonian)
  covering ~130 keys: nav, game table, GameRow, ranked, multiplayer
  lobby/game, scoreboard, login, register, 404, location types, badges
- GameLanguageState now sets DefaultThreadCurrentUICulture on language
  change so IStringLocalizer resolves the correct culture immediately
- MainLayout subscribes to GameLanguageState.Changed and uses
  @key=Language.Code to re-render the active page on switch
- Program.cs reads fg_lang from localStorage before RunAsync so the
  first paint already uses the stored language
- Enabled BlazorWebAssemblyLoadAllGlobalizationData and AddLocalization

Co-authored-by: Cursor <cursoragent@cursor.com>
…port

Generate cached travel stories after valid answers via a pluggable DestinationAi provider (OpenAI, Grok, Claude, Ollama), expose a rate-limited stories API, show a reward panel in all game modes, and add Docker/Aspire production wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
Destination stories are one-shot paragraphs, so Auto now calls OpenAI then local Ollama. Strip Aspire's 10s HTTP resilience handler so those completions are not cancelled mid-request.

Co-authored-by: Cursor <cursoragent@cursor.com>
Enable story generation in UI-selected language (EN/MK), decoupling from place verification language. Update DestinationStories.razor to react to language changes and reload stories. Refactor prompt logic for language-specific instructions. Backend now checks place existence in any language. Add unit tests for prompt and backend logic. Expose internals to test project for improved testability.
- Migrated to story pools with multiple angles per place/language
- Added ToponymStories table and image fields; DB migrations
- Implemented story popovers with place images via new service
- Improved game tables with popovers, images, and new icons
- Added ScoreboardMe page and deterministic player avatars
- Remember last multiplayer room for easy return
- Updated APIs to include user ids for avatars
- Rate-limited and cached place image lookups
- Added unit/integration tests for new features
- Updated localization and refactored code for maintainability
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants