fix(golf): per-course scorecards + library-owner gating + Holes summary - #935
fix(golf): per-course scorecards + library-owner gating + Holes summary#935njrini99-code wants to merge 2 commits into
Conversation
Fixes #913. Problem: the 20 cloud-library golf courses with no human creator (created_by_user_id IS NULL — bulk-seeded rows) all shared ONE cloned 3-tee scorecard (identical Championship/Members/Forward totals AND hole-by-hole pars/yardages/stroke indexes, confirmed live via SQL against the current DB). Any team coach could also Remove or open Edit on those same shared rows, and the course detail sheet had no way to see hole-by-hole data without entering tee-set edit. Fix, three parts: 1. DATA — scripts/seed-course-library-scorecards.ts (REVIEWED-ONLY, not executed) + src/lib/golf/course-scorecard-generator.ts (pure, unit-tested). Hand-set top-line facts (par, championship yardage, rating, slope) per course from well-known public scorecard facts where documented, realistic estimates elsewhere (source/confidence noted per course in the script + PR body); a seeded-per-course generator produces a genuinely distinct 18-hole routing + stroke index for every course, yardages always summing exactly to the chosen total. UPDATE-only, ownership-guarded, idempotent. 2. SAFETY — updateCourse/softDeleteCourse (course-library.ts) now require public admin allowlist membership to edit/remove a library-owned row (created_by_user_id IS NULL); team/user-owned courses keep the existing open-contribution model. UI in CourseDetailDrawer hides Edit/Remove for non-admins on library rows and adds a "Library course" badge. RLS previously allowed ANY authenticated user to UPDATE any golf_courses row underneath the app guard — supabase/migrations/20260717120000_course_library_owner_gate.sql closes that gap (committed for review, NOT applied by this PR) and supabase/tests/rls/golf_course_library.sql locks in the new policy shape. 3. DISCOVERABILITY — new getCourseTeeHoles read action + a read-only "Holes" section (par row + yardage row per tee) in the course detail sheet, so a coach can see the scorecard without opening tee edit. Gates: npx tsc --noEmit -p tsconfig.json (clean); npx eslint (clean); npx vitest run (484 passed, 4 pre-existing skips, 0 new failures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
|
PR title or description contains an excluded keyword. |
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
Warning Review limit reached
Next review available in: 54 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Updates to Preview Branch (feat/course-library-real-data) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
…bump tripwires Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
|
🤖 Mission Control — PR summary What it changes: Per-course scorecards + library-owner gating (#913). The 20 cloud-library courses with Area: GolfHelm ( Risk / reviewers watch:
CI: 4 required gates green (incl. the Supabase RLS-test job). CodeRabbit advisory + |
Fixes #913.
Problem
Three related issues in the Cloud Course Library:
mcp__supabase__execute_sql): 14 courses withsource IS NULL+ 6 withsource='seed'(20 total, all withcreated_by_user_id IS NULL— no human creator) point at an identical 3-tee template — Championship 7,165 yds / 73.6 rating / 140 slope, Members 6,455 / 71.2 / 132, Forward 5,450 / 69.0 / 120 — down to the same hole-by-hole pars, yardages, and stroke indexes for every course, regardless of the real facility (Bethpage Black par 71 was showing par 72; Harbour Town par 71 was showing par 72; etc.). The other 4 active courses in the table (Danville Golf Club, Forsyth Country Club, Pebble Beach Golf Club, Statesville Country Club) already have distinct, real, human-entered data (created_by_user_idset) — those are genuine team/coach contributions, not shared library rows, and are intentionally left untouched.canManageTeam(= userRole === 'coach') gated "Remove course"; "Edit course" wasn't gated by role at all. Neither checked ownership — the same open-contribution model meant to let a coach fix a typo on a course their team added also let any coach rename or delete "Pinehurst No. 2" for every team on the platform. RLS ongolf_coursesUPDATE (golf_courses_update_authenticated, from20260614010000_course_library_update_policy.sql) had the identical gap:USING (auth.uid() IS NOT NULL)— any authenticated user, no ownership check.Fix
1. DATA —
scripts/seed-course-library-scorecards.ts(REVIEWED-ONLY, not executed)src/lib/golf/course-scorecard-generator.ts— pure, unit-tested, deterministic per-course generator. Given a course's top-line facts (par, championship yardage, rating, slope), it produces a full 18-hole routing: which holes are par 3/4/5 (real-world-typical composition per par value), individual yardages (rescaled to SUM EXACTLY to the target total, no rounding drift), and a stroke-index permutation (1=hardest..18=easiest, with a "no par-3 gets index 1 or 2" realism nudge). Seeded on the course name (mulberry32 PRNG over a string hash) so re-running is byte-identical (idempotent) and no two courses ever get the same layout, even at the same par/yardage.scripts/seed-course-library-scorecards.ts— the 20-course fact table + DB-apply logic.--dry-run(default) prints the full diff and writes nothing;--applyperforms writes;--course="Name"scopes to one course for review. UPDATE-only (never inserts a course or tee row — the existing Championship/Members/Forward tee ids are reused, sogolf_rounds.tee_idand already-recorded rounds are untouched), matches bygolf_normalize_name(name)(never a hardcoded id), and re-checkscreated_by_user_id IS NULLbefore touching a row (skips with a warning otherwise — never overwrites a real user's course). Hole rows use upsert-first/prune-surplus-second (the same stage-and-swap pattern asupdateTeeImpl), never a wipe-then-reinsert.Per-course source/confidence notes (also in the script's
sourceNotefield):Left untouched (already real, human-owned data, not library rows): Danville Golf Club, Forsyth Country Club, Pebble Beach Golf Club (distinct from "Pebble Beach Golf Links" above), Statesville Country Club.
2. SAFETY — ownership gating
updateCourse/softDeleteCourse(src/app/golf/actions/course-library.ts) now checkcreated_by_user_id:NULL(library row, no human creator — the same signalcreateCourse/contributeCourseFromRoundalways stamp) requiresSUPER_ADMIN_USER_IDSallowlist membership (same pattern already used insrc/app/golf/actions/auth.ts); a team/user-owned course keeps the existing open-contribution model unchanged.CourseDetailDrawerhides "Edit course" and "Remove course" for non-admins on a library-owned course, and shows a "Library course" badge so it's clear why (not just a vanished button).isSuperAdminis resolved server-side incourses/page.tsx(env-allowlist check, no DB round trip) and threaded throughCourseLibraryClient.supabase/migrations/20260717120000_course_library_owner_gate.sql— committed for review, NOT applied by this PR/agent. RLS previously allowed any authenticated user toUPDATEanygolf_coursesrow (the app-layer guard above doesn't help if something talks to PostgREST directly). The migration tightensgolf_courses_update_authenticatedtocreated_by_user_id IS NOT NULL OR public.is_super_admin(), using the existingis_super_admin()SECURITY DEFINER function (20260701110000_admin_allowlist_is_super_admin.sql).supabase/tests/rls/golf_course_library.sqlgets 2 new pgTAP assertions locking in the policy shape (plan 37 → 39). A human should review + apply this migration via the normal deploy process.Caveat / explicitly out of scope: tee-level "Delete tee"/"Edit tee" (
TeeRowinCourseDetailDrawer,updateTee/softDeleteTee) have the identical gap — a coach can still delete a library course's "Championship" tee set even though they can no longer delete the course itself. The ticket's reported bug was specifically "Remove course (and open edit)"; extending the same ownership check to tees is a natural, small follow-up but is left out here to keep this diff scoped to what was reported.3. DISCOVERABILITY — read-only "Holes" section
getCourseTeeHoles(courseId)read action returns hole rows for every active tee, grouped by tee id (one extra query, kept separate fromgetCourseDetailso the new-round/tee-picker hot paths — which don't render hole data — never pay for it).CourseDetailDrawerrenders a "Holes" section below "Tee sets": one compact table per tee (Hole # / Par / Yds rows), horizontally scrollable, purely presentational (no edit affordance — hole editing stays exclusively in "Edit tee", this just makes the data visible without entering it).Gates
npx tsc --noEmit -p tsconfig.json— clean (0 errors)npx eslint <changed files>— clean (0 errors/warnings)npx vitest run(course-library actions + lib + new generator + course-picker suites) — 484 passed, 4 pre-existing skips, 0 failures, 0 new flakenpx tsx scripts/seed-course-library-scorecards.ts(dry-run only, no DB writes) — verified output totals match the table above exactly (e.g. Bethpage Black Championship totals exactly 7,468 yds)supabase db lint/ pgTAP locally (no Docker in this environment) — CI'ssupabasejob runs both against an ephemeral local stack on this PR.Not done / needs a human
20260717120000_course_library_owner_gate.sqlis not applied to any environment — review then apply via the normal deploy process.scripts/seed-course-library-scorecards.tshas not been run with--applyanywhere — review the fact table (especially the "Low confidence" Poplar Grove entry) then decide when/whether to run it.🤖 Generated with Claude Code
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg