Official platform for DevNation, a university developer club. dn-orbit is a full-stack web application designed to manage club activities, track member contributions (GitHub & LeetCode), and showcase projects.
| # | Module | Key Responsibility |
|---|---|---|
| 1 | Auth & Onboarding | GitHub OAuth, session management, and onboarding profile setup |
| 2 | GitHub Stats | GitHub API integration and statistic caching |
| 3 | LeetCode Stats | LeetCode public GraphQL API integration and caching |
| 4 | Leaderboard | Scoring engine, on-visit drip queue + GitHub webhook, and UI |
| 5 | CMS — Events | Event management (CRUD), registration, and feedback |
| 6 | Members Section | Public member directory, bios, and visibility controls |
| 7 | Admin Panel | Role-Based Access Control (RBAC) and administrative dashboards |
| 8 | Infra & DevOps | Schema management, CI/CD, and Vercel environment setup |
| 9 | Projects Showcase | Project submission, approval workflow, and milestone tracking |
| Layer | Technology |
|---|---|
| Framework | Next.js 16.2.1 (App Router) |
| Language | TypeScript (Strict Mode) |
| UI | React 19.2.4, Tailwind CSS v4 |
| Database | Neon (PostgreSQL, Serverless) |
| ORM | Prisma 7 |
| Auth | NextAuth.js (GitHub OAuth only) |
| Deployment | Vercel |
| Package Manager | Bun |
| Linting | Biome 2 (lint + format) |
app/
(main)/ # public + member-facing pages, shares the landing layout
login/ # sign-in page
onboarding/ # first-run profile form
admin/ # admin-only pages (RBAC enforced)
api/ # Route Handlers only (no page.tsx files)
layout.tsx # root layout
error.tsx # route-level error boundary
not-found.tsx # 404
globals.css
components/
ui/ # primitives — `8bit-*.tsx` are the themed ones actually used
layout/ # header, footer, sidebar
home/ # landing-page sections, modals and the orbit hero
admin/ # admin-panel building blocks
hooks/ # shared client hooks (coverflow, modals, scroll)
lib/
db.ts # Prisma client singleton (import from here)
auth.ts # Auth and session helpers (session, role checks)
sync.ts # stats drip queue
validation.ts # zod schemas shared by the Route Handlers
utils.ts # shared utilities
prisma/
schema.prisma # database schema source of truth
Routing note: this project uses proxy.ts (Next.js 16's replacement for
middleware.ts), and it guards only /onboarding and /admin. Every route
under app/api/** performs its own auth and role checks.
- Install dependencies:
bun install
- Setup environment:
cp .env.example .env # Fill in DATABASE_URL, NEXTAUTH_SECRET, and GITHUB keys - Database initialization:
bunx prisma generate bunx prisma migrate dev
- Run development server:
bun dev
- Singleton Pattern: Always import
dbfrom@/lib/db. - No Raw SQL: All database access must go through Prisma.
- Constraints:
registrations: Unique on(userId, eventId).feedback: Unique on(userId, eventId).project_members: Unique on(projectId, userId).
- Provider: GitHub OAuth is the only supported method.
- Roles:
president,vice_president,core_member,member,alumni,ajiet_student(stored inusers.role; seelib/roles.ts). - Proxy:
proxy.tsenforces auth on/onboardingand/admin/*only. API routes check for themselves. - Onboarding: Users with no
usnare redirected to/onboarding.
- Refresh: There is no cron.
POST /api/syncdrains a small batch of the stalest members per page visit (lib/sync.ts), with per-member TTLs — GitHub 30 min, LeetCode 60 min — and exponential backoff on failure. An optional GitHub org webhook (/api/webhooks/github, see.env.example) marks a member for priority refresh the moment they push, so the board tracks real activity within seconds. - Leaderboard Formula: Recomputed whenever a drain actually changes
something. GH and LC are scores relative to the cohort leader, not raw
counts — a square-root compression keeps one prolific member from flattening
everyone else.
LC Score = (easy×1 + medium×3 + hard×5)(normalised 0–100)GitHub Score = (commits + PRs×2 + stars)(normalised 0–100)Event Score = (attended / total_events) × 100Total = (lcScore × lcWeight) + (githubScore × githubWeight) + (eventScore × eventWeight)
bun run seed:demo is the source of truth for the club's presentable event and
project dataset (scripts/seedDemoContent.ts). It exists because that content
has to be reviewable in a pull request and restorable after someone edits a
record in the admin panel — so the file is authoritative and the database is its
projection.
It does three things, in order: purges a hardcoded list of joke/test rows, then upserts the curated events, projects, rosters, attendance, feedback and audit trail, then recomputes the leaderboard (attendance feeds the event component).
- Idempotent. Seeded rows carry deterministic UUIDv5 ids derived from a slug, and every write is an upsert. Running it twice equals running it once.
- Reversible. Before deleting anything it snapshots the affected rows to
.backups/seed-demo-<timestamp>.json(gitignored). - Bounded. Events and projects are purged by explicit id, never by a predicate. Only audit-log cleanup matches on text, and it prints every line it is about to remove.
- Non-invasive. Member accounts, GitHub/LeetCode stats,
stats_sync_stateand the allowlist are never touched.
bun run seed:demo -- --dry-run # print the whole plan, write nothing
bun run seed:demo # purge + seed + recompute
bun run seed:demo -- --purge-only # remove junk, seed nothing
bun run seed:demo -- --seed-only # skip the purge and storage cleanup
bun run seed:demo -- --skip-storage # leave the Supabase media bucket aloneStorage cleanup removes media-bucket objects orphaned by the purge. It needs
NEXT_PUBLIC_SUPABASE_URL + SUPABASE_PRIVATE_KEY (it also accepts the older
SUPABASE_SERVICE_ROLE_KEY, and falls back to the bucket origin recorded on
existing rows). Without credentials it logs a skip rather than failing the run.
- Exports: Use named exports (except for
page.tsxandlayout.tsx). - Boundaries: Modules must only share data via the database.
- Strict Typing: No
anytypes; no non-null assertions without justification. - Styling: Use Tailwind CSS v4 utility classes exclusively. No inline styles.
The following variables must be configured in your .env file:
DATABASE_URL: Pooled connection string.DIRECT_URL: Direct connection string for migrations.NEXTAUTH_SECRET: Random secret for session encryption.NEXTAUTH_URL: Canonical application URL.GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET: GitHub OAuth credentials.
Member stats (GitHub + LeetCode) and the leaderboard refresh on demand, not on
a cron: a visit to the landing page fires POST /api/sync, which re-syncs the
whole cohort and recomputes leaderboard_scores only when the last run is
older than SYNC_STALE_MINUTES (default 15). A DB lock guarantees a single
runner, so concurrent visitors — or curl — can't stampede the external APIs.
No repository secrets or GitHub Actions required.