Dugout Value
투구·타격·예측 연구와 선수·계약·트레이드 가치를 비교하는 도구 제공
경량 피클볼 리그 도구, 세션 일정, 경기 기록, 선수 순위 관리와 리그 생성·경기 조직·DUPR 등급 추적
Lightweight pickleball league tool for scheduling sessions, tracking match history, and managing player rankings. Create leagues, organize games, and track your DUPR ratings.
스포츠·피트니스TypeScript · JavaScriptGitHub @consumedsoul온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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.