尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Log a round hole by hole at a real Chicago course, see what cost you strokes, and get a practice plan that knows the weather.
Design, Build, Ship (MPCS 51238), Assignment 4. This extends my Assignment 3 tool from a one-device prototype (localStorage) into a multi-user app: classmates create an account, their rounds and practice are saved to a database, and each person sees only their own.
PROGRESS.md · Agent context: AGENTS.md · Flows (from A3): flows.mdflowchart LR
B["Browser<br/>(Next.js app)"]
C["Clerk<br/>sign-in, session token"]
S[("Supabase Postgres<br/>row-level security")]
W["/api/weather<br/>(our server, cached 15 min)"]
O["Open-Meteo<br/>forecast API"]
B -- "sign in / getToken()" --> C
B -- "query + Clerk token" --> S
S -. "verifies the token (third-party auth)<br/>RLS: user_id = token's sub" .-> S
B -- "lat, lon" --> W
W -- "cached per location" --> O
Tracing a save (e.g. finishing a hole):
update() in src/lib/storage.ts).supabase-js. getToken() asks Clerk for the signed-in user's session token, and it goes with the request.user_id must equal the token's sub. The database fills in user_id itself, so the app never sends it.Where the app knows who you are, and where it decides what you see: Clerk knows who you are (the session token, read by src/proxy.ts and useAuth()). Supabase RLS decides what you see. The app never filters by user itself, and there's no secret or service-role key anywhere, so every query runs as you.
Where the API is called, and why: Open-Meteo is called on the server, in src/app/api/weather/route.ts → src/lib/weather-server.ts, never from the browser. It needs no key, so the reasons are:
"use cache" helper keeps each location for 15 minutes, so everyone looking at the same course shares one call. The free tier allows about 10,000 a day. The first call took about 0.74 s; cached ones about 0.03 s.?weather=down to a course page to see that last one.Four tables, each with user_id text default (auth.jwt() ->> 'sub') and row-level security on every table: four policies (select, insert, update, delete) built on one rule, (select auth.jwt() ->> 'sub') = user_id. The anon role has no access.
| Table | Holds |
|---|---|
rounds | One per round: course, tee, holes (JSON), and a weather snapshot at tee time |
practice_sessions | One per session: focus, minutes, feel, drills, challenge score, weather |
custom_courses | Courses you add yourself |
user_settings | One row per person: current focus, weekly goal, hole notes, badges |
The built-in courses, practice facilities and drill videos are shared, read-only reference data, so they ship in the code (src/data/) instead of the database. Schema changes are migrations in supabase/migrations/.
.env.local (git-ignored) and in Vercel's environment variables, never in the code or the repo. .claude/settings.json has a deny rule so the agent can't read .env files.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY). CLERK_SECRET_KEY stays on the server. There is no Supabase secret key at all.Seven Playwright tests (npm run test:e2e) in tests/e2e/. The two test accounts are Clerk +clerk_test users that sign in with one-time tickets, so no passwords are stored anywhere.
| Test | Checks |
|---|---|
two-users.spec.ts | Two golfers each save a course; neither can see the other's |
guest-trial.spec.ts | A guest's trial is saved to the account on sign-in; the banner can be closed |
weather.spec.ts | Weather is saved with a round, and a round still starts when weather is down |
summary-advice.spec.ts | Skipped tee-shot taps aren't counted as missed fairways |
course-search.spec.ts | Typos, word order and nicknames find the right course |
drill-video.spec.ts | Nothing loads from YouTube until you tap "Watch how" |
Watched it fail first. I broke the custom_courses select policy on purpose (using (true)), and the two-user test failed: Expected: 0, Received: 1, because Golfer A's course list showed Golfer B's course. Restoring the real rule made it pass. Both migrations are in the history, and the write-up, with the failing output, is in docs/step7/.
/security-review on the feature branch (51 files): no high- or medium-severity findings. One low-impact note: a guest trial left open in a shared browser tab would be added to whoever signs in next in that same tab.
A UI/UX review of the running app at desktop and phone sizes found, among other things:
All four are fixed, each with a commit.
(Draft: rewrite in my own words before class.)
What I stopped or redirected:
What I caught along the way:
.env.local itself (the brief says to write it by hand). The agent filtered the CLI's output so it never saw a key.npm install
# .env.local (create it yourself):
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
# CLERK_SECRET_KEY=sk_test_...
# NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
# NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_...
npm run dev # http://localhost:3000
npm run test:e2e # Playwright (uses the dev server)
Everything runs on free plans: Vercel Hobby, Supabase Free, Clerk development instance, Open-Meteo, and no payment methods. Supabase pauses a free project after a week without use, so open the app now and then until grades are back.