Dugout Value
Baseball research on pitching, hitting, and projections, plus tools for comparing players, contracts, and trade value.
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Pickled Citizens
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Lightweight web app for running casual pickleball leagues: create leagues, add
players, schedule game sessions, auto-generate balanced doubles matchups, and track
match results with lifetime statistics.檢視完整 README ↗Lightweight web app for running casual pickleball leagues: create leagues, add players, schedule game sessions, auto-generate balanced doubles matchups, and track match results with lifetime statistics.
Live site: pickledcitizens.com
Last updated: 2026-10-30
docs/dupr-sync.md./admin/events (audit log),
/admin/users (create a user with name, DUPR, dupr.com link and league
picks, edit or delete one), and /admin/leagues.| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router, React Server Components), React 18, TypeScript 5.6 |
| Styling | Tailwind CSS 3.4 — editorial B&W design tokens, monospace labels |
| Database | Cloudflare D1 (SQLite) accessed via Drizzle ORM |
| Auth | Clerk (password + magic link), Clerk-hosted UI |
| Hosting | Cloudflare (SSR) via OpenNext for Cloudflare (@opennextjs/cloudflare) |
| CLI / migrations | Wrangler 4 |
| Tests | Vitest |
Note: This project was migrated off Supabase (Postgres + Auth + RLS) to Cloudflare D1 + Clerk. The live schema is the Drizzle definition in
src/lib/db/schema.tswith migrations underdrizzle/.
app/ Next.js App Router pages + API routes
api/
admin/users/ POST/PATCH/DELETE — admin user mgmt (Clerk + D1)
clerkwebhook/ Clerk user lifecycle webhook (svix-verified)
admin/ Super-admin pages (events, users, leagues)
auth/ Clerk SignIn/SignUp + /auth/complete profile finisher
leagues/, sessions/ League + session pages
sitemap.ts Dynamic sitemap (/sitemap.xml)
src/
components/ui/ Reusable UI library (Button, Input, Modal, SectionLabel)
components/sessions/ CreateSessionForm, SessionsList, GuestModal
lib/
db/schema.ts Drizzle schema — source of truth for D1
db/client.ts getDbAsync() — Drizzle client over the Worker DB binding
db/queries/ Per-domain queries with explicit auth checks
actions/ 'use server' actions called from client components
teamGeneration.ts Snaking team-balance algorithm (unit-tested)
standings.ts League board: win % + rivals, derived from results
dupr.ts DUPR formatting, range and profile-link validation
clerk.ts Clerk Backend API client + name length bound
middleware.ts Clerk middleware — CSP nonce + /admin gating
drizzle/ Drizzle-generated D1 migrations
__tests__/ Vitest tests (team generation, authorization, standings, DUPR, actions, admin users, webhook)
docs/dupr-sync.md How the weekly official-DUPR sync runs
Path alias: @/* maps to ./src/*.
npm install
npm run dev # Next.js dev server — NOTE: the D1 binding is NOT available here
npm run preview # OpenNext build + Cloudflare local preview (D1 + Clerk env required)
npm run lint
npm test
For any database work use npm run preview (bindings available) or the Wrangler
local D1 emulator.
Public (committed in wrangler.toml [vars]):
NEXT_PUBLIC_SITE_URL — public site URL (default https://pickledcitizens.com)NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY — Clerk client key (safe to commit)NEXT_PUBLIC_CLERK_SIGN_IN_URL / _SIGN_UP_URL / fallback redirect URLsServer-only (set as Cloudflare secrets — never commit):
CLERK_SECRET_KEY — Clerk Backend API keyCLERK_WEBHOOK_SIGNING_SECRET — verifies the Clerk user lifecycle webhookDISPLAY_TIMEZONE — IANA timezone for session metadata (default America/Los_Angeles)Copy
.env.exampleto.env.localand fill in the values above to run locally. Local.env.localfiles are git-ignored.
D1 is SQLite. The schema lives in src/lib/db/schema.ts (Drizzle). To change it:
# 1. Edit src/lib/db/schema.ts
npx drizzle-kit generate --name <description>
# 2. Apply
wrangler d1 migrations apply pickled-citizens --local # local
wrangler d1 migrations apply pickled-citizens --remote # production
D1 has no row-level security — all authorization is enforced in TypeScript, but not uniformly at the query layer:
src/lib/db/queries/ take the calling user's ID explicitly
and check ownership/membership themselves (canManageSession,
isLeagueOwner).getSessionById, listLeagues, listMembersOfLeague,
listAllProfiles, listAdminEvents and others — take no caller ID and
perform no check. Their caller is responsible for the gate.So when adding a read path, the authorization check belongs in the server action or page, not in the query module. Server actions are POST-reachable RPC endpoints: "the page only renders for members" is not a defense.
Tables: profiles, leagues, league_members, game_sessions, matches, match_players, match_results, session_guests,
admin_events.
The app is built with OpenNext for Cloudflare and deployed via Cloudflare's
Pages GitHub integration on push to main. wrangler.toml declares
pages_build_output_dir = ".open-next/pages" and the D1 binding.
The CI workflow (.github/workflows/cloudflare-deploy.yml) runs typecheck +
lint + tests as a gate; it does not itself deploy.
npm run pages:build # OpenNext build assembled for Cloudflare Pages output
A single super-admin email ([email protected]) is defined in src/lib/constants.ts
as ADMIN_EMAIL and enforced in middleware.ts, src/lib/db/auth-helpers.ts,
and src/components/AdminFooterLinks.tsx. Changing it is a one-line edit — no DB
migration needed.
Proprietary — personal/league use. Reach out via GitHub if you would like to use this codebase for your own league.