diff --git a/.claude/napkin.md b/.claude/napkin.md index adefb514..f93d7442 100644 --- a/.claude/napkin.md +++ b/.claude/napkin.md @@ -68,3 +68,4 @@ | 2026-02-17 | self | Ran `sed` on bracketed Next route paths without quoting, causing zsh `no matches found` | Always single-quote paths containing `[id]` before `sed`/`cat`/`rg` | | 2026-02-17 | self | Queried prod with `queries/users:getByEmailPublic` and failed because deployed function name was `queries/users:getByEmail` | When checking prod Convex, list available functions from error output and call the deployed name instead of assuming local function names | | 2026-02-17 | self | Missed a closing quote in a `sed` command for a bracketed path and got `unmatched '` | Double-check shell quoting when commands include `[id]` paths before execution | +| 2026-02-17 | self | Switched server admin checks to `fetchAuthQuery` but forgot generic type args, causing TS `unknown` on response shape | When using `fetchAuthQuery`, provide explicit generic result typing at call sites | diff --git a/apps/web/app/challenges/[id]/admin/activity-types/page.tsx b/apps/web/app/challenges/[id]/admin/activity-types/page.tsx index 58db922d..3d111f4f 100644 --- a/apps/web/app/challenges/[id]/admin/activity-types/page.tsx +++ b/apps/web/app/challenges/[id]/admin/activity-types/page.tsx @@ -5,6 +5,7 @@ import type { Id } from "@repo/backend/_generated/dataModel"; import { getChallengeOrThrow } from "@/lib/challenge-helpers"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { AdminActivityTypesTable } from "@/components/admin/admin-activity-types-table"; +import { fetchAuthQuery } from "@/lib/server-auth"; interface ActivityTypesAdminPageProps { params: Promise<{ id: string }>; @@ -17,7 +18,7 @@ export default async function ActivityTypesAdminPage({ const { id } = await params; const challenge = await getChallengeOrThrow(id); - const adminStatus = await convex.query(api.queries.participations.isUserChallengeAdmin, { + const adminStatus = await fetchAuthQuery<{ isAdmin: boolean; reason: "global_admin" | "creator" | "challenge_admin" | null }>(api.queries.participations.isUserChallengeAdmin, { challengeId: challenge.id as Id<"challenges">, }); diff --git a/apps/web/app/challenges/[id]/admin/flagged-activities/[activityId]/page.tsx b/apps/web/app/challenges/[id]/admin/flagged-activities/[activityId]/page.tsx index 4fa49816..02f0490b 100644 --- a/apps/web/app/challenges/[id]/admin/flagged-activities/[activityId]/page.tsx +++ b/apps/web/app/challenges/[id]/admin/flagged-activities/[activityId]/page.tsx @@ -7,6 +7,7 @@ import type { Id } from "@repo/backend/_generated/dataModel"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { FlaggedActivityActions } from "@/components/admin/flagged-activity-actions"; +import { fetchAuthQuery } from "@/lib/server-auth"; interface FlaggedActivityDetailPageProps { params: Promise<{ id: string; activityId: string }>; @@ -42,7 +43,7 @@ export default async function FlaggedActivityDetailPage({ notFound(); } - const adminStatus = await convex.query(api.queries.participations.isUserChallengeAdmin, { + const adminStatus = await fetchAuthQuery<{ isAdmin: boolean; reason: "global_admin" | "creator" | "challenge_admin" | null }>(api.queries.participations.isUserChallengeAdmin, { challengeId: detail.activity.challengeId as Id<"challenges">, }); diff --git a/apps/web/app/challenges/[id]/admin/flagged-activities/page.tsx b/apps/web/app/challenges/[id]/admin/flagged-activities/page.tsx index 922e507e..077c30d8 100644 --- a/apps/web/app/challenges/[id]/admin/flagged-activities/page.tsx +++ b/apps/web/app/challenges/[id]/admin/flagged-activities/page.tsx @@ -11,6 +11,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { FlaggingHelpDialog } from "@/components/admin/flagging-help-dialog"; +import { fetchAuthQuery } from "@/lib/server-auth"; interface FlaggedActivitiesPageProps { params: Promise<{ id: string }>; @@ -42,7 +43,7 @@ export default async function FlaggedActivitiesPage({ const searchParamsResolved = await searchParams; const challenge = await getChallengeOrThrow(id); - const adminStatus = await convex.query(api.queries.participations.isUserChallengeAdmin, { + const adminStatus = await fetchAuthQuery<{ isAdmin: boolean; reason: "global_admin" | "creator" | "challenge_admin" | null }>(api.queries.participations.isUserChallengeAdmin, { challengeId: challenge.id as Id<"challenges">, }); diff --git a/apps/web/app/challenges/[id]/admin/integrations/page.tsx b/apps/web/app/challenges/[id]/admin/integrations/page.tsx index 009708f6..25ef2e66 100644 --- a/apps/web/app/challenges/[id]/admin/integrations/page.tsx +++ b/apps/web/app/challenges/[id]/admin/integrations/page.tsx @@ -4,6 +4,7 @@ import type { Id } from "@repo/backend/_generated/dataModel"; import { getChallengeOrThrow } from "@/lib/challenge-helpers"; import { IntegrationsTabs } from "./integrations-tabs"; +import { fetchAuthQuery } from "@/lib/server-auth"; interface IntegrationsAdminPageProps { params: Promise<{ id: string }>; @@ -16,7 +17,7 @@ export default async function IntegrationsAdminPage({ const { id } = await params; const challenge = await getChallengeOrThrow(id); - const adminStatus = await convex.query(api.queries.participations.isUserChallengeAdmin, { + const adminStatus = await fetchAuthQuery<{ isAdmin: boolean; reason: "global_admin" | "creator" | "challenge_admin" | null }>(api.queries.participations.isUserChallengeAdmin, { challengeId: challenge.id as Id<"challenges">, }); diff --git a/apps/web/app/challenges/[id]/admin/layout.tsx b/apps/web/app/challenges/[id]/admin/layout.tsx index 40e23d39..a71cd48f 100644 --- a/apps/web/app/challenges/[id]/admin/layout.tsx +++ b/apps/web/app/challenges/[id]/admin/layout.tsx @@ -9,6 +9,7 @@ import { ArrowLeft } from "lucide-react"; import { requireAuth } from "@/lib/auth"; import { AdminNavigation, type AdminNavigationGroup } from "@/components/admin/admin-navigation"; +import { fetchAuthQuery } from "@/lib/server-auth"; interface ChallengeAdminLayoutProps { children: ReactNode; @@ -32,7 +33,7 @@ export default async function ChallengeAdminLayout({ } // Check if user can manage this challenge - const adminStatus = await convex.query(api.queries.participations.isUserChallengeAdmin, { + const adminStatus = await fetchAuthQuery<{ isAdmin: boolean; reason: "global_admin" | "creator" | "challenge_admin" | null }>(api.queries.participations.isUserChallengeAdmin, { challengeId: id as Id<"challenges">, }); diff --git a/packages/backend/_generated/api.d.ts b/packages/backend/_generated/api.d.ts index e0f85edd..15f1aad2 100644 --- a/packages/backend/_generated/api.d.ts +++ b/packages/backend/_generated/api.d.ts @@ -14,6 +14,7 @@ import type * as actions_createChallengeFromConfig from "../actions/createChalle import type * as actions_fix2025ActivityTypes from "../actions/fix2025ActivityTypes.js"; import type * as actions_fixContributesToStreak from "../actions/fixContributesToStreak.js"; import type * as actions_payments from "../actions/payments.js"; +import type * as actions_rescoreStravaActivities from "../actions/rescoreStravaActivities.js"; import type * as actions_seed from "../actions/seed.js"; import type * as actions_setup2026ActivityTypes from "../actions/setup2026ActivityTypes.js"; import type * as actions_setup2026Challenges from "../actions/setup2026Challenges.js"; @@ -57,6 +58,7 @@ import type * as mutations_miniGames from "../mutations/miniGames.js"; import type * as mutations_participations from "../mutations/participations.js"; import type * as mutations_paymentConfig from "../mutations/paymentConfig.js"; import type * as mutations_payments from "../mutations/payments.js"; +import type * as mutations_rescoreStrava from "../mutations/rescoreStrava.js"; import type * as mutations_stravaWebhook from "../mutations/stravaWebhook.js"; import type * as mutations_templates from "../mutations/templates.js"; import type * as mutations_users from "../mutations/users.js"; @@ -105,6 +107,7 @@ declare const fullApi: ApiFromModules<{ "actions/fix2025ActivityTypes": typeof actions_fix2025ActivityTypes; "actions/fixContributesToStreak": typeof actions_fixContributesToStreak; "actions/payments": typeof actions_payments; + "actions/rescoreStravaActivities": typeof actions_rescoreStravaActivities; "actions/seed": typeof actions_seed; "actions/setup2026ActivityTypes": typeof actions_setup2026ActivityTypes; "actions/setup2026Challenges": typeof actions_setup2026Challenges; @@ -148,6 +151,7 @@ declare const fullApi: ApiFromModules<{ "mutations/participations": typeof mutations_participations; "mutations/paymentConfig": typeof mutations_paymentConfig; "mutations/payments": typeof mutations_payments; + "mutations/rescoreStrava": typeof mutations_rescoreStrava; "mutations/stravaWebhook": typeof mutations_stravaWebhook; "mutations/templates": typeof mutations_templates; "mutations/users": typeof mutations_users;