Thank you for taking the time to contribute. OSSfolio is built entirely by contributors like you — every PR, issue, and discussion makes it better.
- Code of Conduct
- AI Policy
- How Can I Contribute?
- Design System
- Local Setup
- Database — Making Schema Changes
- How Key Systems Work
- Branch Naming
- Commit Messages
- Pull Request Process
- Issue Guidelines
- Good First Issues
- Questions
This project follows our Code of Conduct. By participating, you agree to uphold it.
We are not saying do not use AI. Use it, but use it responsibly.
If you used AI to help write or fix code, that is completely fine. But you need to follow these rules:
Mention it clearly. In your PR description, add a line like: "Used Claude Code for coding" or whichever tool you used. This is not optional.
Actually understand what you changed. Before submitting, you should be able to answer these three questions yourself:
- Which function or module did you change?
- Why did you change it?
- What side effects could that change create?
If you cannot answer those, do not submit yet. Go back, understand the code, then submit.
Write your own PR and issue descriptions. Your PR description and issue messages need to be in your own words. Do not paste AI-generated summaries as your contribution message. These should reflect your actual understanding of the change, not a model's summary of it.
No spam. AI-generated issues, copy-paste PRs, or vague "fix bug" contributions with no real understanding behind them will be closed without review. Quality matters more than speed here.
Open issues labelled bug. Comment to claim one before starting.
Open issues labelled enhancement. Comment explaining your approach and wait to be assigned.
Typos, unclear sections, missing info — no issue needed for small doc fixes. Just open a PR.
Open an issue using the Bug Report template. Include steps to reproduce, expected behaviour, and screenshots if relevant.
Open an issue using the Feature Request template. Describe the problem it solves, not just what you want built.
OSSfolio has a design system documented in DESIGN.md. If your contribution touches any UI — a new component, a page section, buttons, colors, spacing, typography — you need to read it before you start coding.
It covers:
- Color tokens (primary green, ink, canvas, hairline values)
- Typography scale and font weights
- Spacing and border radius values
- Button, card, input, and nav component specs
- What to do and what not to do
Following it keeps the UI consistent across contributions. PRs that introduce new colors, fonts, or spacing outside the design system will be asked to revise.
Fork the repo on GitHub, then clone your fork:
git clone https://github.com/prodhosh/ossfolio.git
cd ossfolionpm installOSSfolio uses Supabase (PostgreSQL) as its backend — it provides the database, authentication, and API. Pick whichever option works for you.
This is the recommended path for most contributors.
- Create a free project at supabase.com
- In your project, go to SQL Editor → New query
- Open
supabase/schema.sqlfrom this repo - Copy the entire contents, paste into the editor, and click Run
- All tables and row-level security policies are created — you're done
Note: This option requires Docker to be installed and running on your machine before you begin.
Use this if you want a fully local setup without a cloud Supabase project.
# Install the Supabase CLI
npm install -g supabase
# Start a local Supabase instance
supabase start
# Apply all migrations and load sample seed data
supabase db resetsupabase db reset runs every file inside supabase/migrations/ in timestamp order, then runs supabase/seed.sql to load sample data. Your local database is fully ready.
The CLI will print your local project URL and anon key — use those in .env.local.
cp .env.example .env.localOpen .env.local and fill in:
| Variable | Where to find it |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase dashboard → Project Settings → API |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase dashboard → Project Settings → API → Project API keys → anon public (this is a safe, public key used to access Supabase from the browser) |
SUPABASE_SERVICE_ROLE_KEY |
Supabase dashboard → Project Settings → API |
NEXTAUTH_SECRET |
Run openssl rand -base64 32 in your terminal |
NEXTAUTH_URL |
http://localhost:3000 for local dev |
GitHub OAuth is configured directly inside Supabase — go to Authentication → Providers → GitHub in your Supabase dashboard and enter your GitHub OAuth app credentials there. You do not need to add them to .env.local.
npm run devOpen http://localhost:3000.
The database schema lives in two places that are always kept in sync:
| File | Purpose |
|---|---|
supabase/schema.sql |
Single master file — paste this into Supabase dashboard to set up everything at once |
supabase/migrations/ |
Individual migration files — used by the Supabase CLI, one file per change |
Do not edit existing migration files. They are immutable once merged — changing them breaks other contributors' local setups.
Instead, create a new migration file:
supabase migration new describe_your_changeThis creates supabase/migrations/<timestamp>_describe_your_change.sql. Write your SQL there.
Then update supabase/schema.sql to reflect the change so dashboard users stay in sync. Both files must be included in your PR.
Reviewers will check the SQL diff before merging.
This section explains how the two most complex systems in the codebase operate: the Supabase authentication flow and the contributor score synchronization pipeline.
OSSfolio uses GitHub OAuth integrated with Supabase for user authentication.
- Initiation: The user clicks "Sign in with GitHub" in the frontend (e.g., AuthModal.tsx).
- Supabase Redirection: Supabase redirects the browser to GitHub's OAuth server.
- GitHub Authentication: The user authorizes the application, and GitHub redirects back to the configured callback URI:
/auth/callback. - Session Resolution: The client component at auth/callback/page.tsx handles the login session.
- Score Sync Trigger: Once the session is successfully resolved, the score sync pipeline is invoked to calculate and cache the user's score.
- Final Redirect: The user is redirected to their public profile page (
/[username]) or the home page (/) if the username metadata is missing.
- What is PKCE?: OSSfolio uses the standard PKCE (Proof Key for Code Exchange) OAuth flow (default in Supabase v2). Under PKCE, the authorization code (
?code=...) in the callback URL is exchanged client-side for an access and refresh token. - Asynchronous Execution: This code exchange happens asynchronously during the Supabase client library's initialization.
onAuthStateChangevsgetSession: Because the exchange is asynchronous, callingsupabase.auth.getSession()immediately upon page load can returnnullbefore the exchange completes. To prevent race conditions, the callback page subscribes to auth state changes usingsupabase.auth.onAuthStateChange. It listens forSIGNED_INandINITIAL_SESSIONevents to ensure that the session is established and active before executing the score sync.- Safety Net: A safety timeout (
AUTH_WAIT_TIMEOUT_MS = 10000) is established to redirect the user back to the home page if the PKCE exchange fails or hangs.
The score sync pipeline calculates the user's contributor score by pulling activity data from GitHub and caching it in the database.
- At Login: Calculated and stored automatically during the post-login OAuth callback phase.
- On-Demand: Regenerated when a user clicks the profile refresh/sync action (which hits
/api/[username]/refreshendpoint). - Timeout Constraint: During the OAuth callback, the
syncScorepipeline is raced against a 4-second timeout (SYNC_TIMEOUT_MS = 4000) to guarantee that slow API requests do not block the user from accessing their profile.
When syncing the score, the application checks for the user's GitHub provider token (saved immediately after OAuth login):
- GraphQL Path (Authenticated): If
providerTokenis available, it queries the GitHub GraphQL API usingfetchContributorProfile. The GraphQL API exposes thecontributionsCollectionquery, which is the only source that returns the user's Pull Request review counts (totalPullRequestReviewContributions). - REST Path (Unauthenticated Fallback): If the token is missing or if the GraphQL query fails (due to rate limits, expired tokens, or scope issues), the pipeline falls back to
statsFromRest(username). This runs three parallel REST Search API requests (fetchLiveStats(username)) to retrieve PR, issue, and commit counts. Because code review counts cannot be retrieved from unauthenticated REST or search APIs, thetotalReviewscount defaults to0in this fallback path.
For full architectural flows and sequence diagrams of these pipelines, consult the System Flow Diagrams and API Reference Architecture.
The calculated score and activity stats are cached in the public.profiles database table. The table columns are:
id(uuid, primary key): Referencesauth.users(id)in Supabase auth system.username(text, unique): User's GitHub login handle.name(text): Display name.avatar_url(text): GitHub avatar image URL.github_url(text): Link to the user's GitHub profile.bio(text): Self-written bio.followers(integer): Number of GitHub followers.top_languages(text[]): Array of top programming languages used by the user.score(integer): Computed contributor score.total_commits(integer): Total commit count.total_prs(integer): Total PR count.total_issues(integer): Total issue count.total_reviews(integer): Total pull request review count (only populated/updated in the GraphQL sync path).badges(jsonb): JSON array of claimant badge configurations.headline(text): Custom profile headline text.pinned_repos(text[]): List of pinned repository names.custom_links(jsonb): User's custom profile links.visibility(text): Visibility state (publicorunlisted).search_text(tsvector): Automatically updated English search vector for full-text profile search.created_at/updated_at(timestamptz): Creation and modification timestamps.view_count(integer): Count of user profile views.last_refreshed_at(timestamptz): Time of the last profile sync.
Use the format type/short-description:
| Prefix | When to use |
|---|---|
feat/ |
New feature |
fix/ |
Bug fix |
docs/ |
Documentation only |
refactor/ |
Code cleanup, no behaviour change |
chore/ |
Tooling, CI, config |
test/ |
Tests only |
Examples: feat/contribution-heatmap, fix/github-api-rate-limit, docs/supabase-setup
This project uses ESLint (flat config v9+) with eslint-config-next to enforce code quality.
Run linting before submitting a PR:
npm run lint # Check for issues
npm run lint:fix # Auto-fix where possibleThe ESLint config is in eslint.config.mjs at the root. Key rules:
no-console: Warn onconsole.log(allowwarn/error)prefer-const: Error onletthat is never reassignedno-unused-vars: Warn on unused variables (ignore_-prefixed)no-duplicate-imports: Error on duplicate imports
TypeScript type-checking is also run in CI:
npm run type-check # tsc --noEmitFollow Conventional Commits:
type(scope): short summary under 72 chars
Examples:
feat(profile): add merged PR count displayfix(api): handle GitHub rate limit gracefullydocs(contributing): clarify supabase setup optionschore(db): add leaderboard migration
Please do not submit a PR without first being assigned to the issue. Comment on the issue with your approach, wait to get assigned, then start working. Once you are assigned, feel free to prepare your PR.
You must use the PR template. When you open a PR on GitHub, the description field is pre-filled with our template automatically. Do not delete it or replace it with your own format. Fill it out completely. PRs that skip the template or leave sections blank will be closed and asked to re-submit.
Once you submit a PR, it will be reviewed within 12 hours. Please be patient and avoid pinging or sending repeated messages asking for a review before that time. You can send one follow-up message after 12 hours, but keep it to that.
- Make sure your branch is up to date with
main - Fill out the PR template completely — incomplete PRs may be closed
- Link the issue using
Closes #<issue-number>in the PR description - Write your PR description in your own words — describe what you changed and why, not just what the diff shows
- If you used AI for any part of the code, mention it clearly in the description
- One logical change per PR — don't bundle unrelated fixes
- All PRs need at least one review before merge
- A maintainer will merge once approved
- I was assigned to the issue before opening this PR
- Code works locally
- No
console.logleft insrc/ - If schema changed — both
schema.sqland a new migration file are included - Docs updated if needed
- PR title follows Conventional Commits format
- PR description is written in my own words
- If I used AI for coding, I mentioned it clearly and I understand every change I made
- Check for duplicates first. Before opening an issue, search the existing open issues to see if someone already reported it. Duplicate issues will be closed.
- Use the correct template. Pick Bug Report, Feature Request, Good First Issue, or Docs depending on what you are filing.
- Be specific. Vague issues are hard to act on and will be asked for more detail or closed.
- No spam. Do not open issues just to get assigned to something without a clear problem statement.
- If you want to work on an issue you opened yourself, say so in the issue and wait to be assigned like everyone else.
New to open source? Start here:
good first issue— beginner-friendly tasks with clear scopedocumentation— great entry point if you're not ready to touch code yet
Not sure where to start? Open a Discussion and ask — we'll help you find something.
Before diving into code, reviewing these documents can save you time:
- Architecture Overview — System design, data flow, theming
- API Reference — All internal API endpoints, params, and errors
- DESIGN.md — Visual design tokens, component specs, spacing
- Database Schema — Tables, policies, and search functions
If you have any doubts, feel free to reach out. Open a Discussion for general questions, or ping once on LinkedIn. Please keep it to one message and give some time for a response before following up.