Files
HIPCTF2/.kilo/plans/910.md
T

6.7 KiB
Raw Blame History

Implementation Plan: Scoreboard: Ranking, Matrix, Event Log and Score Graph 1.00

1. Architectural Reconnaissance

  • Codebase style & conventions: Node.js npm-workspace monorepo with a NestJS 10 backend and Angular 17 standalone frontend, both written in TypeScript. Backend request/business logic is service-oriented; live updates use RxJS-backed NestJS SSE. Frontend feature state uses private Angular signals exposed as read-only signals, with SSE frames normalized in ScoreboardStore and then applied immutably. The Event Log template already renders categoryAbbreviation and challengeName; the defect originates upstream because the solve publish path and authenticated SSE mapper omit those fields, and the frontend currently replaces them with empty strings.
  • Data Layer: TypeORM with better-sqlite3. ChallengesService.submitFlag already joins challenge to category, so the successful transaction has challenge.name and challenge.abbreviation available without an additional query. The reloaded Event Log projection independently joins solve, user, challenge, and category, which explains why labels appear after refresh. No schema or migration is required.
  • Test Framework & Structure: Root Jest 29 multi-project configuration with ts-jest; backend tests are under tests/backend/ using Nest testing utilities/Supertest and frontend tests are under tests/frontend/ using jsdom. All tests run from the repository root with npm test; focused suites can use npm run test:backend and npm run test:frontend. Keep regression coverage in the existing dedicated test folders, not beside source files.
  • Required Tools & Dependencies: No new package, system tool, global CLI, persistent /data fixture, or setup.sh change is required. Existing Node/npm, TypeScript, Jest, NestJS, Angular, RxJS, TypeORM, and SQLite dependencies cover the implementation. Verification should use npm test and npm run build; the repository exposes no lint script.

2. Impacted Files

  • To Modify:
    • backend/src/modules/challenges/dto/challenges.dto.ts — extend the typed live solve contract with challengeName and categoryAbbreviation.
    • backend/src/modules/challenges/challenges.service.ts — include challenge/category labels in the post-insert hub payload using metadata already loaded by submitFlag.
    • backend/src/modules/challenges/events.controller.ts — forward the two labels in authenticated solve SSE frames, preserving the established snake_case wire format and accepting current camelCase hub fields.
    • frontend/src/app/features/scoreboard/scoreboard.pure.ts — extend SolveLivePayload so the normalized live event carries the Event Log labels.
    • frontend/src/app/features/scoreboard/scoreboard.store.ts — parse label fields from SSE aliases and populate the newly prepended EventLogRow instead of writing empty strings.
    • tests/backend/challenges-submit-flag.spec.ts — strengthen the existing hub-publish success-path test to assert challenge name and category abbreviation.
    • tests/frontend/scoreboard.store.spec.ts — add focused regression assertions proving an SSE-pushed Event Log row retains both labels.
    • docs/api/challenges.md — update the documented authenticated solve-frame contract and example fields.
    • docs/guides/scoreboard-stream.md — document that live solve frames carry display metadata needed by the Event Log.
  • To Create: None.

3. Proposed Changes

  1. Database / Schema Migration: Make no database changes. Reuse the challenge/category metadata already selected in ChallengesService.submitFlag; keep the existing reload query in ScoreboardService.getEventLog unchanged.
  2. Backend Logic & APIs:
    1. Add required challengeName and categoryAbbreviation properties to SolveEventPayload so omissions are caught at the backend contract boundary.
    2. Extend the private publishSolveEvent input and its SseHubService.emitScoreboard payload with challenge.name and challenge.abbreviation, passing them from the successful fresh-solve branch only. Preserve current idempotency: already_solved, failed validation, and uniqueness-race fallback paths must not publish a new event.
    3. Extend ChallengesEventsController.solveFrame to emit challenge_name and category_abbreviation alongside challenge_id, player, scoring, and timing data. Do not add a lookup or business logic to the controller; it should remain a pure transport mapping over the enriched hub payload.
    4. Keep all existing fields and aliases intact so Ranking, Matrix, Graph, Challenges Board, and any current stream consumers remain backward-compatible.
  3. Frontend UI Integration:
    1. Extend SolveLivePayload with challengeName and categoryAbbreviation.
    2. In ScoreboardStore.handleSseFrame, normalize both snake_case fields (challenge_name, category_abbreviation) and camelCase fallbacks (challengeName, categoryAbbreviation) in the same style as existing solve properties.
    3. In applySolveEvent, copy those normalized values into the new EventLogRow. Leave EventLogComponent unchanged because its .what span already renders the abbreviation followed by challenge name, and leave the existing deduplication/cap behavior unchanged.
    4. Update API/stream documentation so the public contract matches the corrected wire payload and explains that both initial REST rows and live SSE rows contain identical display metadata.

4. Test Strategy

  • Target Unit Test File:
    • tests/backend/challenges-submit-flag.spec.ts: enhance the existing “emits an SSE solve payload” test to assert the emitted hub event contains the seeded challenge name and CRY abbreviation. This directly verifies the root publish boundary without adding a large SSE/network fixture.
    • tests/frontend/scoreboard.store.spec.ts: extend the existing live solve-frame test, or add one minimal test, with challenge_name and category_abbreviation, then assert the prepended Event Log row contains both exact values while retaining awarded points. This covers the user-visible regression through the stores SSE normalization path without UI/visual testing.
  • Mocking Strategy: Reuse the existing in-memory SQLite Nest application and real SseHubService subscription in the backend test; no external service mocks or persistent files are needed. Reuse FakeApi, the fake DestroyRef, and the lightweight fake EventSourceLike in the frontend test; feed a synthetic MessageEvent and inspect the public read-only eventLog signal. Do not add Playwright, browser, visual, filesystem, network, or /data fixtures. Implement test-first: make the focused assertions fail against the current omission, then apply the smallest payload-contract changes and run the root npm test plus npm run build.