Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ src/assets/data
dist
tests/config.txt
configBackup.json
.env
34 changes: 23 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ The rank column and some aggregate stats are rendered with invisible/white text.
```bash
git clone <repo-url> && cd Opensource-Contribution-Leaderboard
cp src/server/config-example.json src/server/config.json
# Edit config.json with your GitHub token + org + contributors
cp src/server/.env.example src/server/.env
# Edit .env with your GitHub token, org, admin password, etc.
# Edit config.json with your contributors list
npm run add
npm run build
cd dist/server
Expand All @@ -72,26 +74,36 @@ npm run serve # backend API server
```

Frontend: http://localhost:8080
Backend port: whatever `serverPort` is set to in config.json (default 62050)
Backend port: whatever `SERVER_PORT` is set to in .env (default 62050)

## Config Reference

Static/environment settings live in `.env` (copy from `src/server/.env.example`):

```bash
AUTH_TOKEN=ghp_YOUR_GITHUB_TOKEN # required — GitHub personal access token
ORGANIZATION=YourOrg # required — GitHub org name
ORGANIZATION_HOMEPAGE=https://yourorg.com/
ORGANIZATION_GITHUB_URL=https://github.com/YourOrg
ADMIN_PASSWORD=change-this # required — admin panel password
SERVER_PORT=62050 # backend API port
```

Dynamic/runtime values stay in `config.json` (modifiable via admin panel):

```json
{
"organization": "YourOrg",
"organizationHomepage": "https://yourorg.com/",
"organizationGithubUrl": "https://github.com/YourOrg",
"authToken": "ghp_YOUR_GITHUB_TOKEN",
"adminPassword": "change-this",
"delay": "10",
"serverPort": "62050",
"contributors": ["user1", "user2"]
"startDate": "2025-06-01",
"contributors": ["user1", "user2"],
"includedRepositories": ["Repo1", "Repo2"]
}
```

- `authToken` — required. GitHub personal access token with repo read access.
- `delay` — seconds between API calls per contributor (respect rate limits).
- `contributors` — array of GitHub usernames to track. Add users here even before their first contribution.
- `startDate` — filter contributions from this date onwards.
- `contributors` — array of GitHub usernames to track.
- `includedRepositories` — repos to include in contribution tracking.

## Rules for Agents

Expand Down
28 changes: 19 additions & 9 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,34 @@ npm start # webpack-dev-server on :8080
npm run serve # backend on :62050 (in a second terminal)
```

## Config File (src/server/config.json)
## Config

Static/environment settings live in `.env` (copy from `src/server/.env.example`):

```bash
AUTH_TOKEN=ghp_... # GitHub personal access token
ORGANIZATION=YourOrg # GitHub org name
ORGANIZATION_HOMEPAGE=https://yourorg.com/
ORGANIZATION_GITHUB_URL=https://github.com/YourOrg
ADMIN_PASSWORD=change-this # admin panel password
SERVER_PORT=62050 # backend API port
```

Dynamic/runtime values stay in `config.json` (modifiable via admin panel):

```json
{
"organization": "YourOrg",
"organizationHomepage": "https://yourorg.com/",
"organizationGithubUrl": "https://github.com/YourOrg",
"authToken": "ghp_...",
"adminPassword": "change-this",
"delay": "10",
"serverPort": "62050",
"contributors": ["username1", "username2"]
"startDate": "2025-06-01",
"contributors": ["username1", "username2"],
"includedRepositories": ["Repo1", "Repo2"]
}
```

- `authToken` — GitHub personal access token (needed for API rate limits)
- `delay` — seconds between each contributor's API poll
- `startDate` — filter contributions from this date onwards
- `contributors` — GitHub usernames to track
- `includedRepositories` — repos to include in tracking

## Design Decisions You Must Respect

Expand Down
43 changes: 31 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,28 @@ cd Opensource-Contribution-Leaderboard

# 2. Create your config
cp src/server/config-example.json src/server/config.json
cp src/server/.env.example src/server/.env
```

Edit `src/server/config.json`:
Edit `src/server/.env` with your static settings:

```bash
AUTH_TOKEN=ghp_YOUR_GITHUB_TOKEN
ORGANIZATION=YourOrg
ORGANIZATION_HOMEPAGE=https://yourorg.com/
ORGANIZATION_GITHUB_URL=https://github.com/YourOrg
ADMIN_PASSWORD=pick-something-better
SERVER_PORT=62050
```

Edit `src/server/config.json` with dynamic/runtime values:

```json
{
"organization": "YourOrg",
"organizationHomepage": "https://yourorg.com/",
"organizationGithubUrl": "https://github.com/YourOrg",
"authToken": "ghp_YOUR_GITHUB_TOKEN",
"adminPassword": "pick-something-better",
"delay": "10",
"serverPort": "62050",
"contributors": ["contributor1", "contributor2", "contributor3"]
"startDate": "2025-06-01",
"contributors": ["contributor1", "contributor2"],
"includedRepositories": ["Repo1", "Repo2"]
}
```

Expand All @@ -56,14 +64,25 @@ Open **http://localhost:8080** — you're done.

## Config Reference

Static settings live in `.env`:

| Env Variable | What it is |
|---|---|
| `AUTH_TOKEN` | GitHub personal access token (repo read access) |
| `ORGANIZATION` | Your GitHub org name |
| `ORGANIZATION_HOMEPAGE` | Org homepage URL |
| `ORGANIZATION_GITHUB_URL` | Org GitHub URL |
| `ADMIN_PASSWORD` | Password for the admin panel |
| `SERVER_PORT` | Internal backend port (default 62050) |

Dynamic settings live in `config.json` (modifiable via admin panel):

| Key | What it is |
|---|---|
| `organization` | Your GitHub org name |
| `authToken` | GitHub personal access token (repo read access) |
| `adminPassword` | Password for the admin panel |
| `delay` | Seconds between API calls per contributor (respect rate limits) |
| `serverPort` | Internal backend port (default 62050) |
| `startDate` | Filter contributions from this date onwards |
| `contributors` | Array of GitHub usernames to track |
| `includedRepositories` | Repos to include in contribution tracking |

## Local Development

Expand Down
31 changes: 31 additions & 0 deletions src/server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Leaderboard .env configuration
# Copy this file to .env and fill in your values.
# Static/environment-specific settings live here.
# Dynamic values (contributors, delay, startDate, includedRepositories)
# remain in config.json since they are modified at runtime via the admin panel.

# GitHub personal access token (repo read access) — REQUIRED
AUTH_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx

# GitHub organization name — REQUIRED
ORGANIZATION=RocketChat

# Organization homepage URL
ORGANIZATION_HOMEPAGE=https://rocket.chat/

# Organization GitHub URL
ORGANIZATION_GITHUB_URL=https://github.com/RocketChat

# Admin panel password — REQUIRED
ADMIN_PASSWORD=change-this

# Server port for the backend API
SERVER_PORT=62050

# Path overrides (relative to src/server/)
# CONFIG_PATH=./config.json
# ADMINDATA_PATH=./admindata.json
# DATA_PATH=../assets/data/data.json
# LOG_PATH=../assets/data/log.json
# DATA_BASE_PATH=../assets/data
# CONFIG_BACKUP_PATH=../../configBackup.json
32 changes: 18 additions & 14 deletions src/server/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require('dotenv').config()
const http = require('http')
const jsonfile = require('jsonfile')
const url = require('url')
Expand All @@ -10,12 +11,16 @@ const app = express()
const proxy = require('http-proxy-middleware')
const path = require('path')

const configPath = './config.json'
const admindataPath = './admindata.json'
const dataPath = '../assets/data/data.json'
const logPath = '../assets/data/log.json'
const port = jsonfile.readFileSync(configPath).serverPort
const configBackupPath = '../../configBackup.json'
const configPath = process.env.CONFIG_PATH || './config.json'
const admindataPath = process.env.ADMINDATA_PATH || './admindata.json'
const dataPath = process.env.DATA_PATH || '../assets/data/data.json'
const logPath = process.env.LOG_PATH || '../assets/data/log.json'
const port = process.env.SERVER_PORT || 62050
const configBackupPath = process.env.CONFIG_BACKUP_PATH || '../../configBackup.json'
const organization = process.env.ORGANIZATION
const organizationHomepage = process.env.ORGANIZATION_HOMEPAGE
const organizationGithubUrl = process.env.ORGANIZATION_GITHUB_URL
const adminPassword = process.env.ADMIN_PASSWORD
const proxyOption = {
target: 'http://localhost:' + port + '/',
pathRewrite: { '^/api': '' },
Expand Down Expand Up @@ -66,7 +71,6 @@ process.on('exit', () => {
const server = http
.createServer((req, res) => {
const route = url.parse(req.url).pathname
const { adminPassword } = jsonfile.readFileSync(configPath)

switch (route) {
case '/data':
Expand All @@ -84,12 +88,11 @@ const server = http
})
break
case '/config':
var Config = jsonfile.readFileSync(configPath)
res.end(
JSON.stringify({
organization: Config.organization,
organizationHomepage: Config.organizationHomepage,
organizationGithubUrl: Config.organizationGithubUrl,
organization: organization,
organizationHomepage: organizationHomepage,
organizationGithubUrl: organizationGithubUrl,
})
)
break
Expand Down Expand Up @@ -154,7 +157,7 @@ const server = http
res.end('Permission denied\n')
return
}
var { organization, includedRepositories } = jsonfile.readFileSync(
var { includedRepositories } = jsonfile.readFileSync(
configPath
)
API.getRepositories(organization).then((repositories) => {
Expand Down Expand Up @@ -297,9 +300,10 @@ const server = http
// Add this contributor in the data.json
const data = jsonfile.readFileSync(dataPath)
API.getContributorInfo(
Config.organization,
organization,
username,
Config.includedRepositories
Config.includedRepositories,
Config.startDate
).then((result) => {
if (
result.avatarUrl !== '' &&
Expand Down
6 changes: 0 additions & 6 deletions src/server/config-example.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
{
"organization": "RocketChat",
"organizationHomepage": "https://rocket.chat/",
"organizationGithubUrl": "https://github.com/RocketChat",
"authToken": "",
"adminPassword": "123456",
"delay": "10",
"serverPort": "62050",
"contributors": [
"HusseinElFeky",
"rodriq",
Expand Down
1 change: 1 addition & 0 deletions src/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"axios": "^0.18.0",
"bluebird": "^3.5.4",
"chalk": "^2.4.2",
"dotenv": "^16.6.1",
"express": "^4.16.4",
"http-proxy-middleware": "^0.19.1",
"jsonfile": "^5.0.0",
Expand Down
13 changes: 7 additions & 6 deletions src/server/refresh.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
require('dotenv').config()
const Promise = require('bluebird')
const API = require('./util/API')
const jsonfile = require('jsonfile')
const fs = require('fs')

const dataBasePath = '../assets/data'
const dataPath = '../assets/data/data.json'
const logPath = '../assets/data/log.json'
const configPath = './config.json'
const dataBasePath = process.env.DATA_BASE_PATH || '../assets/data'
const dataPath = process.env.DATA_PATH || '../assets/data/data.json'
const logPath = process.env.LOG_PATH || '../assets/data/log.json'
const configPath = process.env.CONFIG_PATH || './config.json'

let interval = 150
let dataBuffer = {}
Expand All @@ -27,9 +28,9 @@ if (fs.existsSync(logPath)) {

async function getAllContributorsInfo() {
let Config = jsonfile.readFileSync(configPath)
let organization = Config.organization
let contributors = Config.contributors
let includedRepositories = Config.includedRepositories
let startDate = Config.startDate

interval = contributors.length < 150 ? 150 : (contributors.length + 10) // update interval

Expand All @@ -40,7 +41,7 @@ async function getAllContributorsInfo() {

await Promise.delay(delay * 1000)

API.getContributorInfo(organization, contributor, includedRepositories).then( res => {
API.getContributorInfo(process.env.ORGANIZATION, contributor, includedRepositories, startDate).then( res => {
Config = jsonfile.readFileSync(configPath) // update Config
delay = Config.delay // update delay

Expand Down
20 changes: 10 additions & 10 deletions src/server/util/API.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const axios = require('axios')
const Config = require('../config.json')
const chalk = require('chalk')

const BASEURL = 'https://github.com'
Expand All @@ -11,7 +10,7 @@ async function get(url, _authToken) {
headers: {
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'GSoC-Contribution-Leaderboard',
Authorization: 'token ' + Config.authToken,
Authorization: 'token ' + process.env.AUTH_TOKEN,
},
})
return new Promise((resolve) => {
Expand All @@ -32,7 +31,7 @@ async function get(url, _authToken) {
case 'Bad credentials':
console.log(
chalk.red(
'[ERROR] Your GitHub Token is not correct! Please check it in the config.json.'
'[ERROR] Your GitHub Token is not correct! Please check the AUTH_TOKEN env variable.'
)
)
process.exit()
Expand Down Expand Up @@ -128,16 +127,17 @@ async function getIssuesNumber(IssuesURL) {
async function getContributorInfo(
organization,
contributor,
includedRepositories
includedRepositories,
startDate
) {
const home = BASEURL + '/' + contributor
const avatarUrl = await getContributorAvatar(contributor)
let OpenPRsURL = `/search/issues?q=is:pr+author:${contributor}+is:Open+created:>=${Config.startDate}`
let openPRsLink = `${BASEURL}/search?q=type:pr+author:${contributor}+is:open+created:>=${Config.startDate}`
let MergedPRsURL = `/search/issues?q=is:pr+author:${contributor}+is:Merged+created:>=${Config.startDate}`
let mergedPRsLink = `${BASEURL}/search?q=type:pr+author:${contributor}+is:merged+created:>=${Config.startDate}`
let IssuesURL = `/search/issues?q=is:issue+author:${contributor}+created:>=${Config.startDate}`
let issuesLink = `${BASEURL}/search?q=type:issue+author:${contributor}+created:>=${Config.startDate}`
let OpenPRsURL = `/search/issues?q=is:pr+author:${contributor}+is:Open+created:>=${startDate}`
let openPRsLink = `${BASEURL}/search?q=type:pr+author:${contributor}+is:open+created:>=${startDate}`
let MergedPRsURL = `/search/issues?q=is:pr+author:${contributor}+is:Merged+created:>=${startDate}`
let mergedPRsLink = `${BASEURL}/search?q=type:pr+author:${contributor}+is:merged+created:>=${startDate}`
let IssuesURL = `/search/issues?q=is:issue+author:${contributor}+created:>=${startDate}`
let issuesLink = `${BASEURL}/search?q=type:issue+author:${contributor}+created:>=${startDate}`
includedRepositories.forEach((repository) => {
openPRsLink += `+repo:${organization}/${repository}`
mergedPRsLink += `+repo:${organization}/${repository}`
Expand Down