feat(gamification): show the real daily streak on the profile page (#12377)

The Profile page already rendered a streak card but fed it a hard-coded useState(0) with a "streak data comes from future API" note — while streaks.ts tracked per-key streaks all along and the MCP gamification_profile tool already returned them. GET /api/gamification/level now returns streak: { current, longest } next to level: the key's own streak with apiKeyId, the operator-wide maximum otherwise, matching the aggregate mode getAggregateXp uses (#3484). No new route, no OpenAPI change, no new i18n keys; a missing or zero streak keeps the card hidden exactly as before.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
This commit is contained in:
Paco Cartones
2026-09-02 08:14:41 +02:00
committed by GitHub
parent 01d97beb8b
commit 4f4aa74199
8 changed files with 303 additions and 5 deletions

View File

@@ -1,6 +1,8 @@
/**
* GET /api/gamification/level — current XP/level for a key, or the operator-wide
* aggregate when no `apiKeyId` is supplied (the dashboard profile page case). (#3484)
* The daily streak rides along in the same payload so the profile streak card can show
* real data without a second round trip. (#2403)
*
* LOCAL_ONLY: not process-spawning; management-scoped via requireManagementAuth.
*/
@@ -8,6 +10,7 @@ import { NextRequest, NextResponse } from "next/server";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { getXp, getAggregateXp } from "@/lib/db/gamification";
import { getStreak, getAggregateStreak } from "@/lib/gamification/streaks";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
export async function OPTIONS() {
@@ -20,5 +23,7 @@ export async function GET(request: NextRequest) {
const apiKeyId = new URL(request.url).searchParams.get("apiKeyId");
const level = apiKeyId ? getXp(apiKeyId) : getAggregateXp();
return NextResponse.json({ level }, { headers: CORS_HEADERS });
const streakData = apiKeyId ? await getStreak(apiKeyId) : await getAggregateStreak();
const streak = { current: streakData.currentStreak, longest: streakData.longestStreak };
return NextResponse.json({ level, streak }, { headers: CORS_HEADERS });
}