Showing preview only (452K chars total). Download the full file or copy to clipboard to get everything.
Repository: avgupta456/github-trends
Branch: main
Commit: 7d1ec357a103
Files: 238
Total size: 399.1 KB
Directory structure:
gitextract_vuc7bnqv/
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── backend.yaml
│ └── frontend.yaml
├── .gitignore
├── LICENSE
├── README.md
├── backend/
│ ├── .coveragerc
│ ├── .env-template
│ ├── .flake8
│ ├── .gcloudignore
│ ├── .pre-commit-config.yaml
│ ├── README.md
│ ├── deploy/
│ │ ├── app.yaml
│ │ ├── cloudbuild.yaml
│ │ └── dispatch.yaml
│ ├── package.json
│ ├── pyproject.toml
│ ├── requirements.txt
│ ├── scripts/
│ │ ├── __init__.py
│ │ ├── delete_old_data.py
│ │ └── local.py
│ ├── src/
│ │ ├── __init__.py
│ │ ├── aggregation/
│ │ │ ├── layer0/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── contributions.py
│ │ │ │ ├── follows.py
│ │ │ │ ├── languages.py
│ │ │ │ └── package.py
│ │ │ ├── layer1/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── auth.py
│ │ │ │ └── user.py
│ │ │ └── layer2/
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ └── user.py
│ │ ├── constants.py
│ │ ├── data/
│ │ │ ├── __init__.py
│ │ │ ├── github/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── auth/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── main.py
│ │ │ │ ├── extensions.json
│ │ │ │ ├── graphql/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── commit.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── repo.py
│ │ │ │ │ ├── template.py
│ │ │ │ │ └── user/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── contribs/
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── contribs.py
│ │ │ │ │ │ └── models.py
│ │ │ │ │ └── follows/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── follows.py
│ │ │ │ │ └── models.py
│ │ │ │ ├── language_map.py
│ │ │ │ ├── rest/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── commit.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── repo.py
│ │ │ │ │ ├── template.py
│ │ │ │ │ └── user.py
│ │ │ │ └── utils.py
│ │ │ └── mongo/
│ │ │ ├── __init__.py
│ │ │ ├── main.py
│ │ │ ├── secret/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── functions.py
│ │ │ │ └── models.py
│ │ │ ├── user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── functions.py
│ │ │ │ ├── get.py
│ │ │ │ └── models.py
│ │ │ └── user_months/
│ │ │ ├── __init__.py
│ │ │ ├── functions.py
│ │ │ ├── get.py
│ │ │ └── models.py
│ │ ├── main.py
│ │ ├── models/
│ │ │ ├── __init__.py
│ │ │ ├── background.py
│ │ │ ├── svg.py
│ │ │ ├── user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── contribs.py
│ │ │ │ ├── follows.py
│ │ │ │ └── main.py
│ │ │ └── wrapped/
│ │ │ ├── __init__.py
│ │ │ ├── calendar.py
│ │ │ ├── langs.py
│ │ │ ├── main.py
│ │ │ ├── numeric.py
│ │ │ ├── repos.py
│ │ │ ├── time.py
│ │ │ └── timestamps.py
│ │ ├── processing/
│ │ │ ├── auth.py
│ │ │ ├── user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── commits.py
│ │ │ │ └── svg.py
│ │ │ └── wrapped/
│ │ │ ├── __init__.py
│ │ │ ├── calendar.py
│ │ │ ├── langs.py
│ │ │ ├── main.py
│ │ │ ├── numeric.py
│ │ │ ├── package.py
│ │ │ ├── repos.py
│ │ │ ├── time.py
│ │ │ └── timestamps.py
│ │ ├── render/
│ │ │ ├── __init__.py
│ │ │ ├── error.py
│ │ │ ├── style.py
│ │ │ ├── template.py
│ │ │ ├── top_langs.py
│ │ │ └── top_repos.py
│ │ ├── routers/
│ │ │ ├── __init__.py
│ │ │ ├── assets/
│ │ │ │ ├── __init__.py
│ │ │ │ └── assets.py
│ │ │ ├── auth/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── main.py
│ │ │ │ ├── standalone.py
│ │ │ │ └── website.py
│ │ │ ├── background.py
│ │ │ ├── decorators.py
│ │ │ ├── dev.py
│ │ │ ├── users/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── db.py
│ │ │ │ ├── main.py
│ │ │ │ └── svg.py
│ │ │ └── wrapped.py
│ │ └── utils/
│ │ ├── __init__.py
│ │ ├── alru_cache.py
│ │ ├── decorators.py
│ │ ├── gather.py
│ │ └── utils.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── aggregation/
│ │ │ ├── __init__.py
│ │ │ └── layer0/
│ │ │ ├── __init__.py
│ │ │ ├── test_contributions.py
│ │ │ └── test_follows.py
│ │ ├── data/
│ │ │ ├── __init__.py
│ │ │ └── github/
│ │ │ ├── __init__.py
│ │ │ ├── auth/
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_main.py
│ │ │ ├── graphql/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_commits.py
│ │ │ │ ├── test_repo.py
│ │ │ │ ├── test_user_contribs.py
│ │ │ │ └── test_user_follows.py
│ │ │ └── rest/
│ │ │ ├── __init__.py
│ │ │ ├── test_commit.py
│ │ │ └── test_repo.py
│ │ └── utils/
│ │ ├── __init__.py
│ │ └── test_alru_cache.py
│ └── transfer_mongodb.bash
├── docs/
│ ├── API.md
│ ├── CONTRIBUTING.md
│ ├── FAQ.md
│ └── THEME.md
└── frontend/
├── .env-template
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── .yarnrc
├── README.md
├── deploy/
│ └── Dockerfile
├── package.json
├── public/
│ ├── _redirects
│ ├── manifest.json
│ ├── robots.txt
│ ├── trends.html
│ └── wrapped.html
├── src/
│ ├── api/
│ │ ├── index.js
│ │ ├── user.js
│ │ └── wrapped.js
│ ├── assets/
│ │ └── notes.txt
│ ├── components/
│ │ ├── Card/
│ │ │ ├── Card.js
│ │ │ ├── SVG.js
│ │ │ └── index.js
│ │ ├── Generic/
│ │ │ ├── Button.js
│ │ │ ├── Checkbox.js
│ │ │ ├── Input.js
│ │ │ └── index.js
│ │ ├── Home/
│ │ │ ├── CheckboxSection.js
│ │ │ ├── DateRangeSection.js
│ │ │ ├── Progress.js
│ │ │ ├── Section.js
│ │ │ └── index.js
│ │ ├── Preview/
│ │ │ ├── Preview.js
│ │ │ └── index.js
│ │ ├── Wrapped/
│ │ │ ├── Organization.js
│ │ │ ├── Specifics/
│ │ │ │ ├── Bar.js
│ │ │ │ ├── Calendar.js
│ │ │ │ ├── Numeric.js
│ │ │ │ ├── Pie.js
│ │ │ │ ├── Radar.js
│ │ │ │ ├── Swarm.js
│ │ │ │ └── index.js
│ │ │ ├── Templates/
│ │ │ │ ├── Bar.js
│ │ │ │ ├── Numeric.js
│ │ │ │ ├── Pie.js
│ │ │ │ ├── Swarm.js
│ │ │ │ ├── index.js
│ │ │ │ └── theme.js
│ │ │ └── index.js
│ │ └── index.js
│ ├── constants.js
│ ├── index.css
│ ├── index.js
│ ├── pages/
│ │ ├── App/
│ │ │ ├── AppTrends.js
│ │ │ ├── AppWrapped.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ └── index.js
│ │ ├── Auth/
│ │ │ ├── SignUp.js
│ │ │ └── index.js
│ │ ├── Demo/
│ │ │ ├── Demo.js
│ │ │ └── index.js
│ │ ├── Home/
│ │ │ ├── Home.js
│ │ │ ├── index.js
│ │ │ └── stages/
│ │ │ ├── Customize.js
│ │ │ ├── Display.js
│ │ │ ├── SelectCard.js
│ │ │ ├── Theme.js
│ │ │ └── index.js
│ │ ├── Landing/
│ │ │ ├── Landing.js
│ │ │ └── index.js
│ │ ├── Misc/
│ │ │ ├── NoMatch.js
│ │ │ ├── Redirect.js
│ │ │ └── index.js
│ │ ├── Settings/
│ │ │ ├── Settings.js
│ │ │ └── index.js
│ │ └── Wrapped/
│ │ ├── SelectUser.js
│ │ ├── Wrapped.js
│ │ ├── index.js
│ │ └── sections/
│ │ ├── Loading.js
│ │ ├── LoadingV2.js
│ │ ├── index.js
│ │ └── loading.css
│ ├── redux/
│ │ ├── actions/
│ │ │ └── userActions.js
│ │ ├── logger.js
│ │ ├── reducers/
│ │ │ ├── index.js
│ │ │ └── user.js
│ │ └── store.js
│ └── utils.js
└── tailwind.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
================================================
FILE: .github/workflows/backend.yaml
================================================
name: CI-Backend
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.11
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install dependencies
run: |
cd backend
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Test with unittest
run: |
cd backend
python -m unittest
env:
AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD }}
- name: Upload coverage to Coveralls
run: |
cd backend
coverage run --source=src -m unittest
coveralls
env:
AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD }}
================================================
FILE: .github/workflows/frontend.yaml
================================================
name: CI-Frontend
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: |
cd frontend
yarn
- name: Run ESLint
run: |
cd frontend
yarn eslint .
================================================
FILE: .gitignore
================================================
*.pyc
__pycache__
.vscode
backend/.env
backend/.venv
backend/.coverage
backend/gcloud_key.json
frontend/.env
.DS_Store
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Abhijit Gupta
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# GitHub Trends
## SPECIAL: GitHub Wrapped
Check out your GitHub Wrapped at `githubwrapped.io`!

---
## What is GitHub Trends
GitHub Trends dives deep into the GitHub API to bring you exciting and impactful metrics about your code contributions. Generate insights on lines written by language, repository, and time. Easily embed dynamic images into your GitHub profile to share your statistics with the world. Check out some of the examples below:
<a href="https://githubtrends.io">
<img align="center" src="https://api.githubtrends.io/user/svg/avgupta456/langs?time_range=one_year&include_private=True&loc_metric=changed" />
</a>
<a href="https://githubtrends.io">
<img align="center" src="https://api.githubtrends.io/user/svg/avgupta456/repos?time_range=one_year&include_private=True&group=private&loc_metric=changed" />
</a>
## Quickstart
First, visit `https://api.githubtrends.io/auth/signup/public` and create an account with GitHub Trends. Then, paste this string into your Markdown content, substituting your username.
```md
[](https://githubtrends.io)
```
And voila, you get a card like above. Keep reading to learn more!
## Why GitHub Trends?
Unlike other projects which look at just your public repositories, GitHub Trends computes metrics based on your individual commits. If you commit to open-source projects, or have collaborators contribute to your own repositories, GitHub Trends will better measure your own code contributions. Through this method, GitHub Trends is the first project that allows users to surface lines of code written (LOC) by language and repository. Our web interface also allows for easier customization.
# Usage
## Website Workflow (Alpha)
Visit [githubtrends.io](https://www.githubtrends.io) to create an account and get started!
Have questions? Check out [the demo](https://www.githubtrends.io/demo)!

---
## API Workflow (Alpha)
Alternatively, you can communicate directly with the API to create and customize your cards. Read [docs/API.md](https://github.com/avgupta456/github-trends/blob/main/docs/API.md) to learn more about the API and customizations.
## FAQ
See [docs/FAQ.md](https://github.com/avgupta456/github-trends/blob/main/docs/FAQ.md).
## Contributing
See [docs/CONTRIBUTING.md](https://github.com/avgupta456/github-trends/blob/main/docs/CONTRIBUTING.md).
## Acknowledgements
Much inspiration was taken from [GitHub Readme Stats](https://github.com/anuraghazra/github-readme-stats). If you haven't already, check it out and give it a star!
================================================
FILE: backend/.coveragerc
================================================
[run]
source = src
omit =
./.venv/*
./tests/*
./models/*
*/__init__.py
[report]
omit =
./.venv/*
./tests/*
./models/*
*/__init__.py
================================================
FILE: backend/.env-template
================================================
AUTH_TOKEN=abc123
COVERALLS_REPO_TOKEN=abc123
PROD_OAUTH_CLIENT_ID=abc123
PROD_OAUTH_CLIENT_SECRET=abc123
PROD_OAUTH_REDIRECT_URI=abc123
DEV_OAUTH_CLIENT_ID=abc123
DEV_OAUTH_CLIENT_SECRET=abc123
DEV_OAUTH_REDIRECT_URI=abc123
GOOGLE_APPLICATION_CREDENTIALS=abc123
MONGODB_PASSWORD=abc123
================================================
FILE: backend/.flake8
================================================
[flake8]
max-line-length = 88
max-complexity = 100
select = B,C,E,F,W,T
ignore = E203, W503, E501
================================================
FILE: backend/.gcloudignore
================================================
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
# Python pycache:
__pycache__/
.venv
.coverage
.coveragerc
.flake8
poetry.lock
pyproject.toml
README.md
# Ignored by the build system
/setup.cfg
# remove irrelevant files/folders
deploy
tests
.env-template
.pre-commit-config.yaml
gcloud_key.json
================================================
FILE: backend/.pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/ambv/black
rev: 21.10b0
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: flake8
args: [--config=./backend/.flake8]
================================================
FILE: backend/README.md
================================================
# Backend
## Installation
```
poetry install
poetry run pre-commit install
```
## Run Locally
Navigate to localhost:8000
```
yarn start
```
## Test with Code Coverage
```
yarn test
```
View coverage with GitHub badge or on coveralls.io
## Build
If a new requirement has been added, make sure to update the requirements.txt
```
yarn set-reqs
```
Then, just commit on the main branch (Google Cloud Run takes care of the rest)
## Adding a Secret
Update cloudbuild.yaml, .env, .env-template, and GCP Cloud Run Trigger Substitution Variables.
================================================
FILE: backend/deploy/app.yaml
================================================
service: default
runtime: python311
entrypoint: gunicorn -w 2 -k uvicorn.workers.UvicornWorker src.main:app
#smallest instance class
instance_class: F1
#prevents creating additional instances
automatic_scaling:
min_instances: 0
max_instances: 1
env_variables:
PROD: true
================================================
FILE: backend/deploy/cloudbuild.yaml
================================================
steps:
- name: node:10.15.1
entrypoint: npm
args: ["install"]
dir: "backend"
- name: node:10.15.1
entrypoint: npm
args: ["run", "create-env"]
dir: "backend"
env:
- "DEV_OAUTH_CLIENT_ID=${_DEV_OAUTH_CLIENT_ID}"
- "DEV_OAUTH_CLIENT_SECRET=${_DEV_OAUTH_CLIENT_SECRET}"
- "DEV_OAUTH_REDIRECT_URI=${_DEV_OAUTH_REDIRECT_URI}"
- "PROD_OAUTH_CLIENT_ID=${_PROD_OAUTH_CLIENT_ID}"
- "PROD_OAUTH_CLIENT_SECRET=${_PROD_OAUTH_CLIENT_SECRET}"
- "PROD_OAUTH_REDIRECT_URI=${_PROD_OAUTH_REDIRECT_URI}"
- "MONGODB_PASSWORD=${_MONGODB_PASSWORD}"
- "SENTRY_DSN=${_SENTRY_DSN}"
- name: "gcr.io/cloud-builders/gcloud"
args: ["app", "deploy", "--appyaml", "./deploy/app.yaml"]
dir: "backend"
================================================
FILE: backend/deploy/dispatch.yaml
================================================
dispatch:
- url: "*/.*"
service: default
================================================
FILE: backend/package.json
================================================
{
"name": "github-trends",
"version": "0.0.1",
"private": true,
"scripts": {
"gen-lang-map": "poetry run python src/data/github/language_map.py",
"start": "poetry run uvicorn src.main:app --reload --port=8000",
"set-reqs": "poetry lock && poetry export -f requirements.txt --output requirements.txt --without-hashes",
"create-env": "printenv > .env",
"test": "poetry run coverage run --source=src -m unittest -v && poetry run coverage report",
"isort": "poetry run isort . --src-path=./src --multi-line=3 --trailing-comma --line-length=88 --combine-as --ensure-newline-before-comments"
}
}
================================================
FILE: backend/pyproject.toml
================================================
[tool.poetry]
name = "github-trends"
version = "0.1.0"
description = ""
authors = ["Abhijit Gupta <avgupta456@gmail.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.11"
fastapi = "^0.104.1"
uvicorn = {extras = ["standard"], version = "^0.24.0.post1"}
requests = "^2.31.0"
python-dotenv = "^1.0.0"
motor = "^3.3.1"
aiofiles = "^23.2.1"
aiounittest = "^1.4.2"
coveralls = "^3.3.1"
grpcio = "^1.59.2"
gunicorn = "^21.2.0"
pymongo = {extras = ["srv"], version = "^4.6.0"}
pytz = "^2023.3.post1"
sentry-sdk = "^1.34.0"
svgwrite = "^1.4.3"
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
black = "^23.11.0"
flake8 = "^6.1.0"
isort = "^5.12.0"
pre-commit = "^3.5.0"
pyinstrument = "^4.6.1"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
================================================
FILE: backend/requirements.txt
================================================
aiofiles==23.2.1
aiounittest==1.4.2
annotated-types==0.6.0
anyio==3.7.1
certifi==2023.11.17
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
coverage==6.5.0
coveralls==3.3.1
dnspython==2.4.2
docopt==0.6.2
fastapi==0.104.1
grpcio==1.59.3
gunicorn==21.2.0
h11==0.14.0
httptools==0.6.1
idna==3.4
motor==3.3.2
packaging==23.2
pydantic-core==2.14.5
pydantic==2.5.2
pymongo==4.6.0
pymongo[srv]==4.6.0
python-dotenv==1.0.0
pytz==2023.3.post1
pyyaml==6.0.1
requests==2.31.0
sentry-sdk==1.36.0
sniffio==1.3.0
starlette==0.27.0
svgwrite==1.4.3
typing-extensions==4.8.0
urllib3==2.1.0
uvicorn[standard]==0.24.0.post1
uvloop==0.19.0
watchfiles==0.21.0
websockets==12.0
wrapt==1.16.0
================================================
FILE: backend/scripts/__init__.py
================================================
================================================
FILE: backend/scripts/delete_old_data.py
================================================
import asyncio
import os
import sys
from datetime import datetime
from typing import Any
from dotenv import find_dotenv, load_dotenv
load_dotenv(find_dotenv())
# Add the parent directory to the Python path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
# flake8: noqa E402
from src.constants import API_VERSION
from src.data.mongo.main import USER_MONTHS
def get_filters(cutoff_date: datetime) -> Any:
return {
"$or": [
{"month": {"$lte": cutoff_date}},
{"version": {"$ne": API_VERSION}},
],
}
async def count_old_rows(cutoff_date: datetime) -> int:
filters = get_filters(cutoff_date)
num_rows = len(await USER_MONTHS.find(filters).to_list(length=None)) # type: ignore
return num_rows
async def delete_old_rows(cutoff_date: datetime):
filters = get_filters(cutoff_date)
result = await USER_MONTHS.delete_many(filters)
print(f"Deleted {result.deleted_count} rows")
async def main():
# Replace 'your_date_field' with the actual name of your date field
cutoff_date = datetime(2024, 12, 31)
count = await count_old_rows(cutoff_date)
if count == 0:
print("No rows to delete.")
return
print(f"Found {count} rows to delete.")
print()
confirmation = input("Are you sure you want to delete these rows? (yes/no): ")
if confirmation.lower() != "yes":
print("Operation canceled.")
return
print()
await delete_old_rows(cutoff_date)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
================================================
FILE: backend/scripts/local.py
================================================
import argparse
import asyncio
import json
import os
import sys
from datetime import datetime
# Add the parent directory to the Python path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
os.environ["LOCAL"] = "True"
# flake8: noqa E402
from src.aggregation.layer0 import get_user_data
from src.processing.user import get_top_languages, get_top_repos
from src.processing.wrapped.package import get_wrapped_data
def parse_args():
parser = argparse.ArgumentParser(description="GitHub Trends Script")
parser.add_argument("--user_id", required=True, help="GitHub user ID", type=str)
parser.add_argument(
"--access_token", required=True, help="GitHub access token", type=str
)
parser.add_argument(
"--start_date",
default="2023-01-01",
help="Start date in YYYY-MM-DD format",
type=str,
)
parser.add_argument(
"--end_date",
default="2023-01-31",
help="End date in YYYY-MM-DD format",
type=str,
)
parser.add_argument(
"--timezone", default="America/New_York", help="Timezone", type=str
)
parser.add_argument(
"--output_dir", default="./", help="Output directory path", type=str
)
return parser.parse_args()
async def main():
args = parse_args()
start_date = datetime.strptime(args.start_date, "%Y-%m-%d")
end_date = datetime.strptime(args.end_date, "%Y-%m-%d")
print("Local script running...")
print("User ID:", args.user_id)
print("Access token:", args.access_token)
print("Start date:", start_date)
print("End date:", end_date)
print("Timezone:", args.timezone)
print("Output directory:", args.output_dir)
print()
raw_output = await get_user_data(
args.user_id, start_date, end_date, args.timezone, args.access_token
)
with open(os.path.join(args.output_dir, "raw.json"), "w") as f:
f.write(raw_output.model_dump_json(indent=2))
langs_output = get_top_languages(
raw_output, loc_metric="changed", include_private=True
)
langs_output = (
[json.loads(x.model_dump_json()) for x in langs_output[0]],
langs_output[1],
)
repos_output = get_top_repos(
raw_output, loc_metric="changed", include_private=True, group="none"
)
repos_output = (
[json.loads(x.model_dump_json()) for x in repos_output[0]],
repos_output[1],
)
with open(os.path.join(args.output_dir, "langs.json"), "w") as f:
f.write(json.dumps(langs_output, indent=2))
with open(os.path.join(args.output_dir, "repos.json"), "w") as f:
f.write(json.dumps(repos_output, indent=2))
wrapped_user = get_wrapped_data(raw_output, 2023)
with open(os.path.join(args.output_dir, "wrapped.json"), "w") as f:
f.write(wrapped_user.model_dump_json(indent=2))
print("Wrote output to", args.output_dir)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
================================================
FILE: backend/src/__init__.py
================================================
================================================
FILE: backend/src/aggregation/layer0/__init__.py
================================================
from src.aggregation.layer0.package import get_user_data
__all__ = ["get_user_data"]
================================================
FILE: backend/src/aggregation/layer0/contributions.py
================================================
from collections import defaultdict
from datetime import date, datetime
from typing import Any, Dict, List, Optional, Tuple, Union
import pytz
from src.aggregation.layer0.languages import CommitLanguages, get_commit_languages
from src.constants import (
GRAPHQL_NODE_CHUNK_SIZE,
GRAPHQL_NODE_THREADS,
NODE_QUERIES,
PR_FILES,
REST_NODE_THREADS,
)
from src.data.github.graphql import (
RawCalendar,
RawCommit as GraphQLRawCommit,
RawEventsCommit,
RawEventsEvent,
RawRepo,
get_commits,
get_repo,
get_user_contribution_calendar,
get_user_contribution_events,
)
from src.data.github.rest import (
RawCommit as RESTRawCommit,
RawCommitFile,
get_commit_files,
get_repo_commits,
)
from src.models import UserContributions
from src.utils import date_to_datetime, gather
class ContribsList:
def __init__(self):
self.commits: List[RawEventsCommit] = []
self.issues: List[RawEventsEvent] = []
self.prs: List[RawEventsEvent] = []
self.reviews: List[RawEventsEvent] = []
self.repos: List[RawEventsEvent] = []
def add(self, label: str, event: Union[RawEventsCommit, RawEventsEvent]):
if label == "commit" and isinstance(event, RawEventsCommit):
self.commits.append(event)
elif label == "issue" and isinstance(event, RawEventsEvent):
self.issues.append(event)
elif label == "pr" and isinstance(event, RawEventsEvent):
self.prs.append(event)
elif label == "review" and isinstance(event, RawEventsEvent):
self.reviews.append(event)
elif label == "repo" and isinstance(event, RawEventsEvent):
self.repos.append(event)
def get_user_all_contribution_events(
user_id: str,
start_date: datetime,
end_date: datetime,
access_token: Optional[str] = None,
) -> Dict[str, ContribsList]:
repo_contribs: Dict[str, ContribsList] = defaultdict(lambda: ContribsList())
after: Optional[str] = ""
cont = True
while cont:
after_str = after if isinstance(after, str) else ""
response = get_user_contribution_events(
user_id=user_id,
start_date=start_date,
end_date=end_date,
after=after_str,
access_token=access_token,
)
cont = False
node_lists = [
("commit", response.commit_contribs_by_repo),
("issue", response.issue_contribs_by_repo),
("pr", response.pr_contribs_by_repo),
("review", response.review_contribs_by_repo),
]
for event_type, event_list in node_lists:
for repo in event_list:
name = repo.repo.name
for event in repo.contribs.nodes:
repo_contribs[name].add(event_type, event)
if repo.contribs.page_info.has_next_page:
after = repo.contribs.page_info.end_cursor
cont = True
for repo in response.repo_contribs.nodes:
name = repo.repo.name
node = RawEventsEvent(occurredAt=repo.occurred_at)
repo_contribs[name].add("repo", node)
return repo_contribs
def get_all_commit_info(
user_id: str,
name_with_owner: str,
start_date: datetime,
end_date: datetime,
access_token: Optional[str] = None,
) -> List[RESTRawCommit]:
owner, repo = name_with_owner.split("/")
data: List[RESTRawCommit] = []
for i in range(10):
if len(data) == 100 * i:
new_data = get_repo_commits(
owner, repo, user_id, start_date, end_date, i + 1, access_token
)
data.extend(new_data)
# sort ascending
return sorted(data, key=lambda x: x.timestamp)
async def get_all_commit_languages(
commit_infos: List[List[RESTRawCommit]],
repos: List[str],
repo_infos: Dict[str, RawRepo],
access_token: Optional[str] = None,
catch_errors: bool = False,
) -> Tuple[Dict[str, List[datetime]], Dict[str, List[CommitLanguages]]]:
commit_node_ids = [[x.node_id for x in repo] for repo in commit_infos]
commit_times = [[x.timestamp for x in repo] for repo in commit_infos]
id_mapping: Dict[str, Tuple[int, int]] = {}
repo_mapping: Dict[str, str] = {}
all_node_ids: List[str] = []
for i, repo_node_ids in enumerate(commit_node_ids):
for j, node_id in enumerate(repo_node_ids):
id_mapping[node_id] = (i, j)
repo_mapping[node_id] = repos[i]
all_node_ids.append(node_id)
node_id_chunks: List[List[str]] = [
all_node_ids[i : min(len(all_node_ids), i + GRAPHQL_NODE_CHUNK_SIZE)]
for i in range(0, len(all_node_ids), GRAPHQL_NODE_CHUNK_SIZE)
]
commit_language_chunks: List[List[Optional[GraphQLRawCommit]]] = await gather(
funcs=[get_commits for _ in node_id_chunks],
args_dicts=[
{
"node_ids": node_id_chunk,
"access_token": access_token,
"catch_errors": catch_errors,
}
for node_id_chunk in node_id_chunks
],
max_threads=GRAPHQL_NODE_THREADS,
)
temp_commit_languages: List[Optional[GraphQLRawCommit]] = []
for commit_language_chunk in commit_language_chunks:
temp_commit_languages.extend(commit_language_chunk)
# returns commits with no associated PR or incomplete PR
filtered_commits: List[GraphQLRawCommit] = filter(
lambda x: x is not None
and (len(x.prs.nodes) == 0 or x.prs.nodes[0].changed_files > PR_FILES)
and (x.additions + x.deletions > 100),
temp_commit_languages,
) # type: ignore
# get NODE_QUERIES largest commits with no associated PR or incomplete PR
sorted_commits = sorted(
filtered_commits, key=lambda x: x.additions + x.deletions, reverse=True
)[:NODE_QUERIES]
sorted_commit_urls = [commit.url.split("/") for commit in sorted_commits]
commit_files: List[List[RawCommitFile]] = await gather(
funcs=[get_commit_files for _ in sorted_commit_urls],
args_dicts=[
{
"owner": url[3],
"repo": url[4],
"sha": url[6],
"access_token": access_token,
}
for url in sorted_commit_urls
],
max_threads=REST_NODE_THREADS,
)
commit_files_dict: Dict[str, List[RawCommitFile]] = {
commit.url: commit_file
for commit, commit_file in zip(sorted_commits, commit_files)
}
commit_languages: List[List[CommitLanguages]] = [
[CommitLanguages() for _ in repo] for repo in commit_infos
]
for raw_commits, node_ids in zip(commit_language_chunks, node_id_chunks):
for raw_commit, node_id in zip(raw_commits, node_ids):
curr_commit_files: Optional[List[RawCommitFile]] = None
if raw_commit is not None and raw_commit.url in commit_files_dict:
curr_commit_files = commit_files_dict[raw_commit.url]
lang_breakdown = get_commit_languages(
raw_commit, curr_commit_files, repo_infos[repo_mapping[node_id]]
)
i, j = id_mapping[node_id]
commit_languages[i][j] = lang_breakdown
commit_times_dict: Dict[str, List[datetime]] = {}
commit_languages_dict: Dict[str, List[CommitLanguages]] = {}
for repo, times, languages in zip(repos, commit_times, commit_languages):
commit_times_dict[repo] = times
commit_languages_dict[repo] = languages
return commit_times_dict, commit_languages_dict
async def get_cleaned_contributions(
user_id: str,
start_date: datetime,
end_date: datetime,
access_token: Optional[str],
catch_errors: bool = False,
) -> Tuple[
RawCalendar,
Dict[str, ContribsList],
Dict[str, RawRepo],
Dict[str, List[datetime]],
Dict[str, List[CommitLanguages]],
]:
calendar = get_user_contribution_calendar(
user_id, start_date, end_date, access_token
)
contrib_events = get_user_all_contribution_events(
user_id, start_date, end_date, access_token
)
repos: List[str] = list(set(contrib_events.keys()))
commit_infos: List[List[RESTRawCommit]] = await gather(
funcs=[get_all_commit_info for _ in repos],
args_dicts=[
{
"user_id": user_id,
"name_with_owner": repo,
"start_date": start_date,
"end_date": end_date,
"access_token": access_token,
}
for repo in repos
],
max_threads=REST_NODE_THREADS,
)
_repo_infos: List[Optional[RawRepo]] = await gather(
funcs=[get_repo for _ in repos],
args_dicts=[
{
"owner": repo.split("/")[0],
"repo": repo.split("/")[1],
"access_token": access_token,
"catch_errors": catch_errors,
}
for repo in repos
],
max_threads=REST_NODE_THREADS,
)
repo_infos = {k: v for k, v in zip(repos, _repo_infos) if v is not None}
commit_times_dict, commit_languages_dict = await get_all_commit_languages(
commit_infos,
repos,
repo_infos,
access_token,
catch_errors,
)
return (
calendar,
contrib_events,
repo_infos,
commit_times_dict,
commit_languages_dict,
)
class StatsContainer:
def __init__(self):
self.contribs: int = 0
self.commits: int = 0
self.issues: int = 0
self.prs: int = 0
self.reviews: int = 0
self.repos: int = 0
self.other: int = 0
self.languages = CommitLanguages()
def add_stat(self, label: str, count: int, add: bool = False) -> None:
if label == "commit":
self.commits += count
elif label == "issue":
self.issues += count
elif label == "pr":
self.prs += count
elif label == "review":
self.reviews += count
elif label == "repo":
self.repos += count
if add:
self.contribs += count
else:
self.other -= count
def to_dict(self) -> Dict[str, Any]:
return {
"contribs_count": self.contribs,
"commits_count": self.commits,
"issues_count": self.issues,
"prs_count": self.prs,
"reviews_count": self.reviews,
"repos_count": self.repos,
"other_count": self.other,
"languages": self.languages.to_dict(),
}
class ListsContainer:
def __init__(self):
self.commits: List[datetime] = []
self.issues: List[datetime] = []
self.prs: List[datetime] = []
self.reviews: List[datetime] = []
self.repos: List[datetime] = []
def add_list(self, label: str, times: List[datetime]) -> None:
if label == "commit":
self.commits.extend(times)
elif label == "issue":
self.issues.extend(times)
elif label == "pr":
self.prs.extend(times)
elif label == "review":
self.reviews.extend(times)
elif label == "repo":
self.repos.extend(times)
def to_dict(self) -> Dict[str, Any]:
return {
"commits": self.commits,
"issues": self.issues,
"prs": self.prs,
"reviews": self.reviews,
"repos": self.repos,
}
class DateContainer:
def __init__(self):
self.date = ""
self.weekday = 0
self.stats = StatsContainer()
self.lists = ListsContainer()
def add_stat(
self, label: str, count: int, times: List[datetime], add: bool = False
):
self.stats.add_stat(label, count, add)
self.lists.add_list(label, times)
def to_dict(self) -> Dict[str, Any]:
return {
"date": self.date,
"weekday": self.weekday,
"stats": self.stats.to_dict(),
"lists": self.lists.to_dict(),
}
# assumed one month span, can be no more than one year
async def get_contributions(
user_id: str,
start_date: date,
end_date: date,
timezone_str: str = "US/Eastern",
access_token: Optional[str] = None,
catch_errors: bool = False,
) -> UserContributions:
tz = pytz.timezone(timezone_str)
start_month = date_to_datetime(start_date)
end_month = date_to_datetime(end_date, hour=23, minute=59, second=59)
(
calendar,
contrib_events,
repo_infos,
commit_times_dict,
commit_languages_dict,
) = await get_cleaned_contributions(
user_id, start_month, end_month, access_token, catch_errors
)
total_stats = StatsContainer()
public_stats = StatsContainer()
total: Dict[str, DateContainer] = defaultdict(DateContainer)
public: Dict[str, DateContainer] = defaultdict(DateContainer)
repo_stats: Dict[str, StatsContainer] = defaultdict(StatsContainer)
repositories: Dict[str, Dict[str, DateContainer]] = defaultdict(
lambda: defaultdict(DateContainer)
)
for week in calendar.weeks:
for day in week.contribution_days:
day_str = str(day.date)
for obj, stats_obj in [(total, total_stats), (public, public_stats)]:
obj[day_str].date = day.date.isoformat()
obj[day_str].weekday = day.weekday
obj[day_str].stats.contribs = day.count
obj[day_str].stats.other = day.count
stats_obj.contribs += day.count
stats_obj.other += day.count
def update_stats(
date_str: str, repo: str, event: str, count: int, times: List[datetime]
):
# update global counts for this event
total[date_str].add_stat(event, count, times)
total_stats.add_stat(event, count)
if not repo_infos[repo].is_private:
public[date_str].add_stat(event, count, times)
public_stats.add_stat(event, count)
repositories[repo][date_str].add_stat(event, count, times, add=True)
repo_stats[repo].add_stat(event, count, add=True)
def update_langs(date_str: str, repo: str, langs: CommitLanguages):
stores = [
total[date_str].stats.languages,
total_stats.languages,
repositories[repo][date_str].stats.languages,
repo_stats[repo].languages,
]
if not repo_infos[repo].is_private:
stores.append(public[date_str].stats.languages)
stores.append(public_stats.languages)
for store in stores:
store += langs
for repo, repo_events in contrib_events.items():
for label, events in [
("commit", repo_events.commits),
("issue", repo_events.issues),
("pr", repo_events.prs),
("review", repo_events.reviews),
("repo", repo_events.repos),
]:
events = sorted(events, key=lambda x: x.occurred_at)
for event in events:
datetime_obj = event.occurred_at.astimezone(tz)
date_str = datetime_obj.date().isoformat()
repositories[repo][date_str].date = date_str
if isinstance(event, RawEventsCommit):
count = 0
commit_times: List[datetime] = []
while len(commit_languages_dict[repo]) > 0 and count < event.count:
commit_times.append(commit_times_dict[repo].pop(0))
langs = commit_languages_dict[repo].pop(0)
update_langs(date_str, repo, langs)
count += 1
update_stats(date_str, repo, "commit", event.count, commit_times)
else:
update_stats(date_str, repo, label, 1, [datetime_obj])
total_stats_dict = total_stats.to_dict()
public_stats_dict = public_stats.to_dict()
repo_stats_dict = {name: stats.to_dict() for name, stats in repo_stats.items()}
for repo in repo_stats:
repo_stats_dict[repo]["private"] = repo_infos[repo].is_private
total_list = [v.to_dict() for v in total.values() if v.stats.contribs > 0]
public_list = [v.to_dict() for v in public.values() if v.stats.contribs > 0]
repositories_list = {
name: [v.to_dict() for v in repo.values()]
for name, repo in repositories.items()
}
output = UserContributions.model_validate(
{
"total_stats": total_stats_dict,
"public_stats": public_stats_dict,
"total": total_list,
"public": public_list,
"repo_stats": repo_stats_dict,
"repos": repositories_list,
}
)
return output
================================================
FILE: backend/src/aggregation/layer0/follows.py
================================================
from typing import List, Optional
from src.data.github.graphql import (
get_user_followers as _get_user_followers,
get_user_following as _get_user_following,
)
from src.models import User, UserFollows
def get_user_follows(user_id: str, access_token: Optional[str]) -> UserFollows:
"""get user followers and users following for given user"""
followers: List[User] = []
following: List[User] = []
for user_list, get_func in zip(
[followers, following], [_get_user_followers, _get_user_following]
):
after: Optional[str] = ""
index, cont = 0, True # initialize variables
while cont and index < 10:
after_str: str = after if isinstance(after, str) else ""
data = get_func(user_id, after=after_str, access_token=access_token)
cont = False
user_list.extend(
map(
lambda x: User(name=x.name, login=x.login, url=x.url),
data.nodes,
)
)
if data.page_info.has_next_page:
after = data.page_info.end_cursor
cont = True
index += 1
return UserFollows(followers=followers, following=following)
================================================
FILE: backend/src/aggregation/layer0/languages.py
================================================
from json import load
from typing import Any, Dict, List, Optional, Union
from src.constants import BLACKLIST, CUTOFF, DEFAULT_COLOR, FILE_CUTOFF
from src.data.github.graphql import RawCommit, RawRepo
from src.data.github.rest import RawCommitFile
EXTENSIONS: Dict[str, Dict[str, str]] = load(open("./src/data/github/extensions.json"))
class CommitLanguages:
def __init__(self):
self.langs: Dict[str, Dict[str, Union[str, int]]] = {}
def __repr__(self):
return str(self.langs)
def add_lines(
self, name: str, color: Optional[str], additions: int, deletions: int
):
if (
name not in BLACKLIST
and max(additions, deletions) > 0
and max(additions, deletions) < FILE_CUTOFF
):
color = color or DEFAULT_COLOR
if name not in self.langs:
self.langs[name] = {"color": color, "additions": 0, "deletions": 0}
self.langs[name]["additions"] += additions # type: ignore
self.langs[name]["deletions"] += deletions # type: ignore
def normalize(self, add_ratio: float, del_ratio: float):
for lang in self.langs:
new_add = round(int(self.langs[lang]["additions"]) * add_ratio)
self.langs[lang]["additions"] = new_add
new_del = round(int(self.langs[lang]["deletions"]) * del_ratio)
self.langs[lang]["deletions"] = new_del
def __add__(self, other: "CommitLanguages"):
for lang in other.langs:
if lang not in self.langs:
self.langs[lang] = other.langs[lang].copy()
else:
self.langs[lang]["additions"] += other.langs[lang]["additions"] # type: ignore
self.langs[lang]["deletions"] += other.langs[lang]["deletions"] # type: ignore
def to_dict(self) -> Dict[str, Any]:
return self.langs
def get_commit_languages(
commit: Optional[RawCommit],
files: Optional[List[RawCommitFile]],
repo: RawRepo,
) -> CommitLanguages:
out = CommitLanguages()
if commit is None:
return out
if max(commit.additions, commit.deletions) == 0:
return out
# assummed to be auto-generated or copied
if max(commit.additions, commit.deletions) > 10 * CUTOFF or (
max(commit.additions, commit.deletions) > CUTOFF
and min(commit.additions, commit.deletions) == 0
):
return out
pr_coverage = 0
if len(commit.prs.nodes) > 0:
pr_obj = commit.prs.nodes[0]
pr_files = pr_obj.files.nodes
total_changed = sum(x.additions + x.deletions for x in pr_files)
pr_coverage = total_changed / max(1, (pr_obj.additions + pr_obj.deletions))
if files is not None:
for file in files:
filename = file.filename.split(".")
extension = "" if len(filename) <= 1 else filename[-1]
lang = EXTENSIONS.get(f".{extension}", None)
if lang is not None:
out.add_lines(
lang["name"], lang["color"], file.additions, file.deletions
)
elif len(commit.prs.nodes) > 0 and pr_coverage > 0.25:
pr = commit.prs.nodes[0]
total_additions, total_deletions = 0, 0
for file in pr.files.nodes:
filename = file.path.split(".")
extension = "" if len(filename) <= 1 else filename[-1]
lang = EXTENSIONS.get(f".{extension}", None)
if lang is not None:
out.add_lines(
lang["name"], lang["color"], file.additions, file.deletions
)
total_additions += file.additions
total_deletions += file.deletions
add_ratio = min(pr.additions, commit.additions) / max(1, total_additions)
del_ratio = min(pr.deletions, commit.deletions) / max(1, total_deletions)
out.normalize(add_ratio, del_ratio)
elif commit.additions + commit.deletions > 2 * CUTOFF:
# assummed to be auto generated
return out
else:
repo_info = repo.languages.edges
languages = [x for x in repo_info if x.node.name not in BLACKLIST]
total_repo_size = sum(language.size for language in languages)
for language in languages:
lang_name = language.node.name
lang_color = language.node.color
lang_size = language.size
additions = round(commit.additions * lang_size / total_repo_size)
deletions = round(commit.deletions * lang_size / total_repo_size)
out.add_lines(lang_name, lang_color, additions, deletions)
return out
================================================
FILE: backend/src/aggregation/layer0/package.py
================================================
from datetime import date
from typing import Optional
from src.aggregation.layer0.contributions import get_contributions
from src.models import UserPackage
# from src.subscriber.aggregation.user.follows import get_user_follows
async def get_user_data(
user_id: str,
start_date: date,
end_date: date,
timezone_str: str,
access_token: Optional[str],
catch_errors: bool = False,
) -> UserPackage:
"""packages all processing steps for the user query"""
contribs = await get_contributions(
user_id=user_id,
start_date=start_date,
end_date=end_date,
timezone_str=timezone_str,
access_token=access_token,
catch_errors=catch_errors,
)
return UserPackage(contribs=contribs)
================================================
FILE: backend/src/aggregation/layer1/__init__.py
================================================
from src.aggregation.layer1.user import query_user
__all__ = ["query_user"]
================================================
FILE: backend/src/aggregation/layer1/auth.py
================================================
from datetime import timedelta
from typing import List, Optional, Tuple
from src.constants import OWNER, REPO
from src.data.github.rest import (
RESTError,
RESTErrorNotFound,
get_repo_stargazers as github_get_repo_stargazers,
get_user as github_get_user,
get_user_starred_repos as github_get_user_starred_repos,
)
from src.data.github.utils import get_access_token
from src.data.mongo.user import get_public_user as db_get_public_user
from src.utils import alru_cache
async def get_valid_github_user(user_id: str) -> Optional[str]:
access_token = get_access_token()
try:
return github_get_user(user_id, access_token)["login"]
except (RESTErrorNotFound, KeyError):
# User does not exist
return None
except RESTError:
# Rate limited, so assume user exists
return user_id
async def get_valid_db_user(user_id: str) -> bool:
user = await db_get_public_user(user_id)
return user is not None
@alru_cache(ttl=timedelta(minutes=15))
async def get_repo_stargazers(
owner: str = OWNER, repo: str = REPO, no_cache: bool = False
) -> Tuple[bool, List[str]]:
access_token = get_access_token()
data: List[str] = []
page = 0
while len(data) == 100 * page:
temp_data = github_get_repo_stargazers(access_token, owner, repo, page=page)
temp_data = [x["user"]["login"] for x in temp_data]
data.extend(temp_data)
page += 1
return (True, data)
async def get_user_stars(user_id: str) -> List[str]:
access_token = get_access_token()
try:
data = github_get_user_starred_repos(user_id, access_token)
data = [x["repo"]["full_name"] for x in data]
return data
except RESTErrorNotFound:
# User does not exist (and rate limited previously)
return []
except RESTError:
# Rate limited, so assume user starred repo
return [f"{OWNER}/{REPO}"]
================================================
FILE: backend/src/aggregation/layer1/user.py
================================================
from calendar import monthrange
from datetime import date, datetime, timedelta
from typing import List, Optional, Tuple
import requests
from src.aggregation.layer0.package import get_user_data
from src.constants import API_VERSION # , BACKEND_URL, PROD
from src.data.github.graphql import GraphQLErrorRateLimit
from src.data.mongo.secret import update_keys
from src.data.mongo.user_months import UserMonth, get_user_months, set_user_month
from src.models.user.main import UserPackage
from src.utils import alru_cache, date_to_datetime
s = requests.Session()
# Formerly the subscriber, compute and save new data here
async def query_user_month(
user_id: str,
access_token: Optional[str],
private_access: bool,
start_date: date,
retries: int = 0,
) -> Optional[UserMonth]:
year, month = start_date.year, start_date.month
end_day = monthrange(year, month)[1]
end_date = date(year, month, end_day)
try:
data = await get_user_data(
user_id=user_id,
start_date=start_date,
end_date=end_date,
timezone_str="US/Eastern",
access_token=access_token,
catch_errors=retries > 0,
)
except GraphQLErrorRateLimit:
return None
except Exception:
# Retry, catching exceptions and marking incomplete this time
if retries < 1:
await query_user_month(
user_id, access_token, private_access, start_date, retries + 1
)
return None
month_completed = datetime.now() > date_to_datetime(end_date) + timedelta(days=1)
user_month = UserMonth.model_validate(
{
"user_id": user_id,
"month": date_to_datetime(start_date),
"version": API_VERSION,
"private": private_access,
"complete": retries == 0 and month_completed,
"data": data,
}
)
await set_user_month(user_month)
return user_month
@alru_cache(ttl=timedelta(hours=6))
async def query_user(
user_id: str,
access_token: Optional[str],
private_access: bool = False,
start_date: date = date.today() - timedelta(365),
end_date: date = date.today(),
max_time: int = 3600, # seconds
no_cache: bool = False,
) -> Tuple[bool, UserPackage]:
# Return (possibly incomplete) within max_time seconds
start_time = datetime.now()
incomplete = False
await update_keys()
curr_data: List[UserMonth] = await get_user_months(
user_id, private_access, start_date, end_date
)
curr_months = [x.month for x in curr_data if x.complete]
month, year = start_date.month, start_date.year
new_months: List[date] = []
while date(year, month, 1) <= end_date:
start = date(year, month, 1)
if date_to_datetime(start) not in curr_months:
new_months.append(start)
month = month % 12 + 1
year = year + (month == 1)
# Start with complete months and add any incomplete months
all_user_packages: List[UserPackage] = [x.data for x in curr_data if x.complete]
for month in new_months:
if datetime.now() - start_time < timedelta(seconds=max_time):
temp = await query_user_month(user_id, access_token, private_access, month)
if temp is not None:
all_user_packages.append(temp.data)
else:
incomplete = True
else:
incomplete = True
out: UserPackage = UserPackage.empty()
if len(all_user_packages) > 0:
out = all_user_packages[0]
for user_package in all_user_packages[1:]:
out += user_package
out.incomplete = incomplete
if incomplete or len(new_months) > 1:
# TODO: figure out why this causes an infinite loop
# # cache buster for publisher
# if PROD:
# s.get(f"{BACKEND_URL}/user/{user_id}?no_cache=True")
return (False, out)
# only cache if just the current month updated
return (True, out)
================================================
FILE: backend/src/aggregation/layer2/__init__.py
================================================
from src.aggregation.layer2.auth import get_is_valid_user
from src.aggregation.layer2.user import get_user, get_user_demo
__all__ = ["get_is_valid_user", "get_user", "get_user_demo"]
================================================
FILE: backend/src/aggregation/layer2/auth.py
================================================
from datetime import timedelta
from typing import Optional, Tuple
from src.aggregation.layer1.auth import (
get_repo_stargazers,
get_user_stars,
get_valid_db_user,
get_valid_github_user,
)
from src.constants import OWNER, REPO, USER_BLACKLIST, USER_WHITELIST
from src.data.github.rest import RESTError
from src.utils import alru_cache
async def check_github_user_exists(user_id: str) -> Optional[str]:
return await get_valid_github_user(user_id)
async def check_db_user_exists(user_id: str) -> bool:
return await get_valid_db_user(user_id)
async def check_user_starred_repo(
user_id: str, owner: str = OWNER, repo: str = REPO
) -> bool:
# Checks the repo's starred users (with cache)
try:
repo_stargazers = await get_repo_stargazers(owner, repo)
if user_id in repo_stargazers:
return True
except RESTError:
return True # Assume the user has starred the repo
# Checks the user's 30 most recent starred repos (no cache)
user_stars = await get_user_stars(user_id)
return f"{owner}/{repo}" in user_stars
@alru_cache(ttl=timedelta(hours=1))
async def get_is_valid_user(user_id: str) -> Tuple[bool, str]:
if user_id.lower() in USER_BLACKLIST:
# TODO: change error message
return (False, "GitHub user not found")
if user_id.lower() in USER_WHITELIST:
return (True, f"Valid user {user_id.lower()}")
valid_user_id = await check_github_user_exists(user_id)
if valid_user_id is None:
return (False, "GitHub user not found")
valid_db_user = await check_db_user_exists(valid_user_id)
user_starred = await check_user_starred_repo(valid_user_id)
if not (user_starred or valid_db_user):
return (False, "Repo not starred")
return (True, f"Valid user {valid_user_id}")
================================================
FILE: backend/src/aggregation/layer2/user.py
================================================
from datetime import date, timedelta
from typing import Optional, Tuple
from src.aggregation.layer0 import get_user_data
from src.constants import USER_BLACKLIST
from src.data.mongo.secret.functions import update_keys
from src.data.mongo.user import PublicUserModel, get_public_user as db_get_public_user
from src.data.mongo.user_months import get_user_months
from src.models import UserPackage
from src.models.background import UpdateUserBackgroundTask
from src.utils import alru_cache
# Formerly the publisher, loads existing data here
async def _get_user(
user_id: str, private_access: bool, start_date: date, end_date: date
) -> Tuple[Optional[UserPackage], bool]:
user_months = await get_user_months(user_id, private_access, start_date, end_date)
if len(user_months) == 0:
return None, False
expected_num_months = (
(end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + 1
)
complete = len(user_months) == expected_num_months
user_data = user_months[0].data
for user_month in user_months[1:]:
user_data += user_month.data
# TODO: handle timezone_str here
return user_data.trim(start_date, end_date), complete
@alru_cache()
async def get_user(
user_id: str,
start_date: date,
end_date: date,
no_cache: bool = False,
) -> Tuple[
bool, Tuple[Optional[UserPackage], bool, Optional[UpdateUserBackgroundTask]]
]:
if user_id in USER_BLACKLIST:
return (False, (None, False, None))
user: Optional[PublicUserModel] = await db_get_public_user(user_id)
if user is None:
return (False, (None, False, None))
private_access = user.private_access or False
user_data, complete = await _get_user(user_id, private_access, start_date, end_date)
background_task = UpdateUserBackgroundTask(
user_id=user_id,
access_token=user.access_token,
private_access=private_access,
start_date=start_date,
end_date=end_date,
)
return (complete, (user_data, complete, background_task))
@alru_cache(ttl=timedelta(minutes=15))
async def get_user_demo(
user_id: str, start_date: date, end_date: date, no_cache: bool = False
) -> Tuple[bool, UserPackage]:
await update_keys()
timezone_str = "US/Eastern"
# recompute/cache but don't save to db
data = await get_user_data(
user_id=user_id,
start_date=start_date,
end_date=end_date,
timezone_str=timezone_str,
access_token=None,
catch_errors=True,
)
return (True, data)
================================================
FILE: backend/src/constants.py
================================================
import os
# GLOBAL
LOCAL = os.getenv("LOCAL", "False") == "True"
PROD = os.getenv("PROD", "False") == "True"
PROJECT_ID = "github-334619"
BACKEND_URL = "https://api.githubtrends.io" if PROD else "http://localhost:8000"
OWNER = "avgupta456"
REPO = "github-trends"
# API
# https://docs.github.com/en/rest/reference/rate-limit
# https://docs.github.com/en/rest/guides/best-practices-for-integrators#dealing-with-secondary-rate-limits
# https://docs.github.com/en/graphql/overview/resource-limitations
TIMEOUT = 15 # max seconds to wait for api response
GRAPHQL_NODE_CHUNK_SIZE = 50 # number of nodes (commits) to query (max 100)
GRAPHQL_NODE_THREADS = 5 # number of node queries simultaneously (avoid blacklisting)
REST_NODE_THREADS = 50 # number of node queries simultaneously (avoid blacklisting)
PR_FILES = 5 # max number of files to query for PRs
NODE_QUERIES = 20 # max number of node queries to make
CUTOFF = 1000 # if additions or deletions > CUTOFF, or sum > 2 * CUTOFF, ignore LOC
FILE_CUTOFF = 1000 # if less than cutoff in file, count LOC
API_VERSION = 0.02 # determines when to overwrite MongoDB data
# CUSTOMIZATION
BLACKLIST = ["Jupyter Notebook", "HTML"] # languages to ignore
# OAUTH
prefix = "PROD" if PROD else "DEV"
# client ID for GitHub OAuth App
OAUTH_CLIENT_ID = os.getenv(f"{prefix}_OAUTH_CLIENT_ID", "")
# client secret for App
OAUTH_CLIENT_SECRET = os.getenv(f"{prefix}_OAUTH_CLIENT_SECRET", "")
# redirect uri for App
OAUTH_REDIRECT_URI = os.getenv(f"{prefix}_OAUTH_REDIRECT_URI", "")
# MONGODB
MONGODB_PASSWORD = os.getenv("MONGODB_PASSWORD", "")
# SVG
DEFAULT_COLOR = "#858585"
# SENTRY
SENTRY_DSN = os.getenv("SENTRY_DSN", "")
# TESTING
TEST_USER_ID = "avgupta456"
TEST_REPO = "github-trends"
TEST_TOKEN = os.getenv("AUTH_TOKEN", "") # for authentication
TEST_NODE_IDS = [
"C_kwDOENp939oAKGM1MzdlM2QzMTZjMmEyZGIyYWU4ZWI0MmNmNjQ4YWEwNWQ5OTBiMjM",
"C_kwDOD_-BVNoAKDFhNTIxNWE1MGM4ZDllOGEwYTFhNjhmYWZkYzE5MzA5YTRkMDMwZmM",
"C_kwDOD_-BVNoAKDRiZTQ4MTQ0MzgwYjBlNGEwNjQ4YjY4YWI4ZjFjYmQ3MWU4M2VhMzU",
]
TEST_SHA = "ad83e6340377904fa0295745b5314202b23d2f3f"
# WRAPPED
# example users, don't need to star the repo
USER_WHITELIST = [
"torvalds",
"yyx990803",
"shadcn",
"sindresorhus",
]
USER_BLACKLIST = ["kangmingtay", "ae7er", "stalukdar7", "piyush7833"]
print("PROD", PROD)
print("API_VERSION", API_VERSION)
print()
================================================
FILE: backend/src/data/__init__.py
================================================
================================================
FILE: backend/src/data/github/__init__.py
================================================
================================================
FILE: backend/src/data/github/auth/__init__.py
================================================
from src.data.github.auth.main import authenticate
__all__ = ["authenticate"]
================================================
FILE: backend/src/data/github/auth/main.py
================================================
from datetime import datetime
from typing import Dict, Optional, Tuple
import requests
from src.constants import OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI
s = requests.session()
def get_unknown_user(access_token: str) -> Optional[str]:
"""
Accepts access_token and returns user_id of associated user
:param access_token: GitHub access token
:return: user_id or None if invalid access_token
"""
headers: Dict[str, str] = {
"Accept": "application/vnd.github.v3+json",
"Authorization": f"bearer {access_token}",
}
r = s.get("https://api.github.com/user", params={}, headers=headers)
return r.json().get("login", None)
class OAuthError(Exception):
pass
async def authenticate(code: str) -> Tuple[str, str]:
"""
Takes a authentication code, verifies, and returns user_id/access_token
:param code: GitHub authentication code from OAuth process
:return: user_id, access_token of authenticated user
"""
start = datetime.now()
params = {
"client_id": OAUTH_CLIENT_ID,
"client_secret": OAUTH_CLIENT_SECRET,
"code": code,
"redirect_uri": OAUTH_REDIRECT_URI,
}
r = s.post("https://github.com/login/oauth/access_token", params=params)
if r.status_code != 200:
raise OAuthError(f"OAuth Error: {str(r.status_code)}")
access_token = r.text.split("&")[0].split("=")[1]
user_id = get_unknown_user(access_token)
if user_id is None:
raise OAuthError("OAuth Error: Invalid user_id/access_token")
print("OAuth SignUp", datetime.now() - start)
return user_id, access_token
================================================
FILE: backend/src/data/github/extensions.json
================================================
{
".1": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".1in": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".1m": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".1x": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".2": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".3": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".3in": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".3m": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".3p": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".3pm": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".3qt": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".3x": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".4": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".4dm": {
"color": "#004289",
"name": "4D"
},
".4th": {
"color": "#341708",
"name": "Forth"
},
".5": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".6": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".6pl": {
"color": "#0000fb",
"name": "Raku"
},
".6pm": {
"color": "#0000fb",
"name": "Raku"
},
".7": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".8": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".8xk": {
"color": "#A0AA87",
"name": "TI Program"
},
".8xk.txt": {
"color": "#A0AA87",
"name": "TI Program"
},
".8xp": {
"color": "#A0AA87",
"name": "TI Program"
},
".8xp.txt": {
"color": "#A0AA87",
"name": "TI Program"
},
".9": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".E": {
"color": "#ccce35",
"name": "E"
},
"._coffee": {
"color": "#244776",
"name": "CoffeeScript"
},
"._js": {
"color": "#f1e05a",
"name": "JavaScript"
},
"._ls": {
"color": "#499886",
"name": "LiveScript"
},
".a51": {
"color": "#6E4C13",
"name": "Assembly"
},
".abap": {
"color": "#E8274B",
"name": "ABAP"
},
".ada": {
"color": "#02f88c",
"name": "Ada"
},
".adb": {
"color": "#02f88c",
"name": "Ada"
},
".ado": {
"color": "#1a5f91",
"name": "Stata"
},
".adp": {
"color": "#e4cc98",
"name": "Tcl"
},
".ads": {
"color": "#02f88c",
"name": "Ada"
},
".agc": {
"color": "#0B3D91",
"name": "Apollo Guidance Computer"
},
".agda": {
"color": "#315665",
"name": "Agda"
},
".ahk": {
"color": "#6594b9",
"name": "AutoHotkey"
},
".ahkl": {
"color": "#6594b9",
"name": "AutoHotkey"
},
".aidl": {
"color": "#34EB6B",
"name": "AIDL"
},
".aj": {
"color": "#a957b0",
"name": "AspectJ"
},
".al": {
"color": "#0298c3",
"name": "Perl"
},
".als": {
"color": "#64C800",
"name": "Alloy"
},
".ampl": {
"color": "#E6EFBB",
"name": "AMPL"
},
".angelscript": {
"color": "#C7D7DC",
"name": "AngelScript"
},
".apib": {
"color": "#2ACCA8",
"name": "API Blueprint"
},
".apl": {
"color": "#5A8164",
"name": "APL"
},
".app.src": {
"color": "#B83998",
"name": "Erlang"
},
".applescript": {
"color": "#101F1F",
"name": "AppleScript"
},
".arc": {
"color": "#aa2afe",
"name": "Arc"
},
".as": {
"color": "#C7D7DC",
"name": "AngelScript"
},
".asax": {
"color": "#9400ff",
"name": "ASP.NET"
},
".asc": {
"color": "#B9D9FF",
"name": "AGS Script"
},
".ascx": {
"color": "#9400ff",
"name": "ASP.NET"
},
".asd": {
"color": "#3fb68b",
"name": "Common Lisp"
},
".asddls": {
"color": "#555e25",
"name": "ABAP CDS"
},
".ash": {
"color": "#B9D9FF",
"name": "AGS Script"
},
".ashx": {
"color": "#9400ff",
"name": "ASP.NET"
},
".asm": {
"color": "#005daa",
"name": "Motorola 68K Assembly"
},
".asmx": {
"color": "#9400ff",
"name": "ASP.NET"
},
".asp": {
"color": "#6a40fd",
"name": "Classic ASP"
},
".aspx": {
"color": "#9400ff",
"name": "ASP.NET"
},
".astro": {
"color": "#ff5a03",
"name": "Astro"
},
".asy": {
"color": "#ff0000",
"name": "Asymptote"
},
".au3": {
"color": "#1C3552",
"name": "AutoIt"
},
".aug": {
"color": "#9CC134",
"name": "Augeas"
},
".auk": {
"color": "#c30e9b",
"name": "Awk"
},
".aux": {
"color": "#3D6117",
"name": "TeX"
},
".aw": {
"color": "#4F5D95",
"name": "PHP"
},
".awk": {
"color": "#c30e9b",
"name": "Awk"
},
".axd": {
"color": "#9400ff",
"name": "ASP.NET"
},
".axi": {
"color": "#0aa0ff",
"name": "NetLinx"
},
".axi.erb": {
"color": "#747faa",
"name": "NetLinx+ERB"
},
".axs": {
"color": "#0aa0ff",
"name": "NetLinx"
},
".axs.erb": {
"color": "#747faa",
"name": "NetLinx+ERB"
},
".b": {
"color": "#2F2530",
"name": "Brainfuck"
},
".bal": {
"color": "#FF5000",
"name": "Ballerina"
},
".bas": {
"color": "#867db1",
"name": "VBA"
},
".bash": {
"color": "#89e051",
"name": "Shell"
},
".bat": {
"color": "#C1F12E",
"name": "Batchfile"
},
".bats": {
"color": "#89e051",
"name": "Shell"
},
".bb": {
"color": "#00FFAE",
"name": "BlitzBasic"
},
".bbx": {
"color": "#3D6117",
"name": "TeX"
},
".bdy": {
"color": "#dad8d8",
"name": "PLSQL"
},
".bf": {
"color": "#2F2530",
"name": "Brainfuck"
},
".bi": {
"color": "#867db1",
"name": "FreeBasic"
},
".bib": {
"color": "#778899",
"name": "BibTeX"
},
".bibtex": {
"color": "#778899",
"name": "BibTeX"
},
".bicep": {
"color": "#519aba",
"name": "Bicep"
},
".bison": {
"color": "#6A463F",
"name": "Bison"
},
".blade": {
"color": "#f7523f",
"name": "Blade"
},
".blade.php": {
"color": "#f7523f",
"name": "Blade"
},
".bmx": {
"color": "#cd6400",
"name": "BlitzMax"
},
".bones": {
"color": "#f1e05a",
"name": "JavaScript"
},
".boo": {
"color": "#d4bec1",
"name": "Boo"
},
".boot": {
"color": "#db5855",
"name": "Clojure"
},
".bpl": {
"color": "#c80fa0",
"name": "Boogie"
},
".brs": {
"color": "#662D91",
"name": "Brightscript"
},
".bsl": {
"color": "#814CCC",
"name": "1C Enterprise"
},
".bsv": {
"color": "#12223c",
"name": "Bluespec"
},
".builder": {
"color": "#701516",
"name": "Ruby"
},
".bzl": {
"color": "#76d275",
"name": "Starlark"
},
".c": {
"color": "#555555",
"name": "C"
},
".c++": {
"color": "#f34b7d",
"name": "C++"
},
".cake": {
"color": "#244776",
"name": "CoffeeScript"
},
".capnp": {
"color": "#c42727",
"name": "Cap'n Proto"
},
".cats": {
"color": "#555555",
"name": "C"
},
".cbx": {
"color": "#3D6117",
"name": "TeX"
},
".cc": {
"color": "#f34b7d",
"name": "C++"
},
".cdf": {
"color": "#dd1100",
"name": "Mathematica"
},
".ceylon": {
"color": "#dfa535",
"name": "Ceylon"
},
".cfc": {
"color": "#ed2cd6",
"name": "ColdFusion CFC"
},
".cfm": {
"color": "#ed2cd6",
"name": "ColdFusion"
},
".cfml": {
"color": "#ed2cd6",
"name": "ColdFusion"
},
".cgi": {
"color": "#89e051",
"name": "Shell"
},
".cginc": {
"color": "#aace60",
"name": "HLSL"
},
".ch": {
"color": "#403a40",
"name": "xBase"
},
".chpl": {
"color": "#8dc63f",
"name": "Chapel"
},
".cirru": {
"color": "#ccccff",
"name": "Cirru"
},
".cjs": {
"color": "#f1e05a",
"name": "JavaScript"
},
".cjsx": {
"color": "#244776",
"name": "CoffeeScript"
},
".ck": {
"color": "#3f8000",
"name": "ChucK"
},
".cl": {
"color": "#ed2e2d",
"name": "OpenCL"
},
".cl2": {
"color": "#db5855",
"name": "Clojure"
},
".click": {
"color": "#E4E6F3",
"name": "Click"
},
".clj": {
"color": "#db5855",
"name": "Clojure"
},
".cljc": {
"color": "#db5855",
"name": "Clojure"
},
".cljs": {
"color": "#db5855",
"name": "Clojure"
},
".cljs.hl": {
"color": "#db5855",
"name": "Clojure"
},
".cljscm": {
"color": "#db5855",
"name": "Clojure"
},
".cljx": {
"color": "#db5855",
"name": "Clojure"
},
".clp": {
"color": "#00A300",
"name": "CLIPS"
},
".cls": {
"color": "#867db1",
"name": "VBA"
},
".clw": {
"color": "#db901e",
"name": "Clarion"
},
".cmake": {
"color": "#DA3434",
"name": "CMake"
},
".cmake.in": {
"color": "#DA3434",
"name": "CMake"
},
".cmd": {
"color": "#C1F12E",
"name": "Batchfile"
},
".cnc": {
"color": "#D08CF2",
"name": "G-code"
},
".cocci": {
"color": "#c94949",
"name": "SmPL"
},
".coffee": {
"color": "#244776",
"name": "CoffeeScript"
},
".coffee.md": {
"color": "#244776",
"name": "Literate CoffeeScript"
},
".command": {
"color": "#89e051",
"name": "Shell"
},
".coq": {
"color": "#d0b68c",
"name": "Coq"
},
".cp": {
"color": "#B0CE4E",
"name": "Component Pascal"
},
".cpp": {
"color": "#f34b7d",
"name": "C++"
},
".cps": {
"color": "#B0CE4E",
"name": "Component Pascal"
},
".cr": {
"color": "#000100",
"name": "Crystal"
},
".cs": {
"color": "#178600",
"name": "C#"
},
".csd": {
"color": "#1a1a1a",
"name": "Csound Document"
},
".cshtml": {
"color": "#512be4",
"name": "HTML+Razor"
},
".css": {
"color": "#563d7c",
"name": "CSS"
},
".csx": {
"color": "#178600",
"name": "C#"
},
".ctp": {
"color": "#4F5D95",
"name": "PHP"
},
".cu": {
"color": "#3A4E3A",
"name": "Cuda"
},
".cue": {
"color": "#5886E1",
"name": "CUE"
},
".cuh": {
"color": "#3A4E3A",
"name": "Cuda"
},
".cwl": {
"color": "#B5314C",
"name": "Common Workflow Language"
},
".cxx": {
"color": "#f34b7d",
"name": "C++"
},
".d": {
"color": "#427819",
"name": "Makefile"
},
".dart": {
"color": "#00B4AB",
"name": "Dart"
},
".dats": {
"color": "#1ac620",
"name": "ATS"
},
".db2": {
"color": "#e38c00",
"name": "SQLPL"
},
".dcl": {
"color": "#3F85AF",
"name": "Clean"
},
".ddl": {
"color": "#dad8d8",
"name": "PLSQL"
},
".decls": {
"color": "#00FFAE",
"name": "BlitzBasic"
},
".dfm": {
"color": "#E3F171",
"name": "Pascal"
},
".dfy": {
"color": "#FFEC25",
"name": "Dafny"
},
".dhall": {
"color": "#dfafff",
"name": "Dhall"
},
".di": {
"color": "#ba595e",
"name": "D"
},
".djs": {
"color": "#cca760",
"name": "Dogescript"
},
".dlm": {
"color": "#a3522f",
"name": "IDL"
},
".dm": {
"color": "#447265",
"name": "DM"
},
".do": {
"color": "#1a5f91",
"name": "Stata"
},
".dockerfile": {
"color": "#384d54",
"name": "Dockerfile"
},
".doh": {
"color": "#1a5f91",
"name": "Stata"
},
".dpr": {
"color": "#E3F171",
"name": "Pascal"
},
".druby": {
"color": "#c7a938",
"name": "Mirah"
},
".dsp": {
"color": "#c37240",
"name": "Faust"
},
".dtx": {
"color": "#3D6117",
"name": "TeX"
},
".duby": {
"color": "#c7a938",
"name": "Mirah"
},
".dwl": {
"color": "#003a52",
"name": "DataWeave"
},
".dyalog": {
"color": "#5A8164",
"name": "APL"
},
".dyl": {
"color": "#6c616e",
"name": "Dylan"
},
".dylan": {
"color": "#6c616e",
"name": "Dylan"
},
".e": {
"color": "#4d6977",
"name": "Eiffel"
},
".ebuild": {
"color": "#9400ff",
"name": "Gentoo Ebuild"
},
".ec": {
"color": "#913960",
"name": "eC"
},
".ecl": {
"color": "#001d9d",
"name": "ECLiPSe"
},
".eclass": {
"color": "#9400ff",
"name": "Gentoo Eclass"
},
".eclxml": {
"color": "#8a1267",
"name": "ECL"
},
".ecr": {
"color": "#2e1052",
"name": "HTML+ECR"
},
".ect": {
"color": "#a91e50",
"name": "EJS"
},
".eex": {
"color": "#6e4a7e",
"name": "HTML+EEX"
},
".eh": {
"color": "#913960",
"name": "eC"
},
".ejs": {
"color": "#a91e50",
"name": "EJS"
},
".el": {
"color": "#c065db",
"name": "Emacs Lisp"
},
".eliom": {
"color": "#3be133",
"name": "OCaml"
},
".eliomi": {
"color": "#3be133",
"name": "OCaml"
},
".elm": {
"color": "#60B5CC",
"name": "Elm"
},
".em": {
"color": "#FFF4F3",
"name": "EmberScript"
},
".emacs": {
"color": "#c065db",
"name": "Emacs Lisp"
},
".emacs.desktop": {
"color": "#c065db",
"name": "Emacs Lisp"
},
".emberscript": {
"color": "#FFF4F3",
"name": "EmberScript"
},
".env": {
"color": "#89e051",
"name": "Shell"
},
".eps": {
"color": "#da291c",
"name": "PostScript"
},
".epsi": {
"color": "#da291c",
"name": "PostScript"
},
".eq": {
"color": "#a78649",
"name": "EQ"
},
".erb": {
"color": "#701516",
"name": "HTML+ERB"
},
".erb.deface": {
"color": "#701516",
"name": "HTML+ERB"
},
".erl": {
"color": "#B83998",
"name": "Erlang"
},
".es": {
"color": "#f1e05a",
"name": "JavaScript"
},
".es6": {
"color": "#f1e05a",
"name": "JavaScript"
},
".escript": {
"color": "#B83998",
"name": "Erlang"
},
".ex": {
"color": "#6e4a7e",
"name": "Elixir"
},
".exs": {
"color": "#6e4a7e",
"name": "Elixir"
},
".eye": {
"color": "#701516",
"name": "Ruby"
},
".f": {
"color": "#4d41b1",
"name": "Fortran"
},
".f03": {
"color": "#4d41b1",
"name": "Fortran Free Form"
},
".f08": {
"color": "#4d41b1",
"name": "Fortran Free Form"
},
".f77": {
"color": "#4d41b1",
"name": "Fortran"
},
".f90": {
"color": "#4d41b1",
"name": "Fortran Free Form"
},
".f95": {
"color": "#4d41b1",
"name": "Fortran Free Form"
},
".factor": {
"color": "#636746",
"name": "Factor"
},
".fan": {
"color": "#14253c",
"name": "Fantom"
},
".fancypack": {
"color": "#7b9db4",
"name": "Fancy"
},
".fcgi": {
"color": "#89e051",
"name": "Shell"
},
".feature": {
"color": "#5B2063",
"name": "Gherkin"
},
".fish": {
"color": "#4aae47",
"name": "fish"
},
".flex": {
"color": "#DBCA00",
"name": "JFlex"
},
".flux": {
"color": "#88ccff",
"name": "FLUX"
},
".fnc": {
"color": "#dad8d8",
"name": "PLSQL"
},
".fnl": {
"color": "#fff3d7",
"name": "Fennel"
},
".for": {
"color": "#4d41b1",
"name": "Fortran"
},
".forth": {
"color": "#341708",
"name": "Forth"
},
".fp": {
"color": "#5686a5",
"name": "GLSL"
},
".fpp": {
"color": "#4d41b1",
"name": "Fortran"
},
".fr": {
"color": "#00cafe",
"name": "Frege"
},
".frag": {
"color": "#f1e05a",
"name": "JavaScript"
},
".frg": {
"color": "#5686a5",
"name": "GLSL"
},
".frm": {
"color": "#867db1",
"name": "VBA"
},
".frt": {
"color": "#341708",
"name": "Forth"
},
".frx": {
"color": "#867db1",
"name": "VBA"
},
".fs": {
"color": "#5686a5",
"name": "GLSL"
},
".fsh": {
"color": "#5686a5",
"name": "GLSL"
},
".fshader": {
"color": "#5686a5",
"name": "GLSL"
},
".fsi": {
"color": "#b845fc",
"name": "F#"
},
".fst": {
"color": "#572e30",
"name": "F*"
},
".fsx": {
"color": "#b845fc",
"name": "F#"
},
".fth": {
"color": "#341708",
"name": "Forth"
},
".ftl": {
"color": "#0050b2",
"name": "FreeMarker"
},
".fun": {
"color": "#dc566d",
"name": "Standard ML"
},
".fut": {
"color": "#5f021f",
"name": "Futhark"
},
".fx": {
"color": "#aace60",
"name": "HLSL"
},
".fxh": {
"color": "#aace60",
"name": "HLSL"
},
".fy": {
"color": "#7b9db4",
"name": "Fancy"
},
".g": {
"color": "#0000cc",
"name": "GAP"
},
".g4": {
"color": "#9DC3FF",
"name": "ANTLR"
},
".gaml": {
"color": "#FFC766",
"name": "GAML"
},
".gap": {
"color": "#0000cc",
"name": "GAP"
},
".gawk": {
"color": "#c30e9b",
"name": "Awk"
},
".gco": {
"color": "#D08CF2",
"name": "G-code"
},
".gcode": {
"color": "#D08CF2",
"name": "G-code"
},
".gd": {
"color": "#355570",
"name": "GDScript"
},
".gemspec": {
"color": "#701516",
"name": "Ruby"
},
".geo": {
"color": "#5686a5",
"name": "GLSL"
},
".geom": {
"color": "#5686a5",
"name": "GLSL"
},
".gf": {
"color": "#ff0000",
"name": "Grammatical Framework"
},
".gi": {
"color": "#0000cc",
"name": "GAP"
},
".glf": {
"color": "#c1ac7f",
"name": "Glyph"
},
".glsl": {
"color": "#5686a5",
"name": "GLSL"
},
".glslf": {
"color": "#5686a5",
"name": "GLSL"
},
".glslv": {
"color": "#5686a5",
"name": "GLSL"
},
".gml": {
"color": "#71b417",
"name": "Game Maker Language"
},
".gms": {
"color": "#f49a22",
"name": "GAMS"
},
".gnu": {
"color": "#f0a9f0",
"name": "Gnuplot"
},
".gnuplot": {
"color": "#f0a9f0",
"name": "Gnuplot"
},
".go": {
"color": "#00ADD8",
"name": "Go"
},
".god": {
"color": "#701516",
"name": "Ruby"
},
".golo": {
"color": "#88562A",
"name": "Golo"
},
".gp": {
"color": "#f0a9f0",
"name": "Gnuplot"
},
".grace": {
"color": "#615f8b",
"name": "Grace"
},
".groovy": {
"color": "#4298b8",
"name": "Groovy"
},
".grt": {
"color": "#4298b8",
"name": "Groovy"
},
".gs": {
"color": "#f1e05a",
"name": "JavaScript"
},
".gshader": {
"color": "#5686a5",
"name": "GLSL"
},
".gsp": {
"color": "#4298b8",
"name": "Groovy Server Pages"
},
".gst": {
"color": "#82937f",
"name": "Gosu"
},
".gsx": {
"color": "#82937f",
"name": "Gosu"
},
".gtpl": {
"color": "#4298b8",
"name": "Groovy"
},
".gvy": {
"color": "#4298b8",
"name": "Groovy"
},
".gyp": {
"color": "#3572A5",
"name": "Python"
},
".gypi": {
"color": "#3572A5",
"name": "Python"
},
".h": {
"color": "#438eff",
"name": "Objective-C"
},
".h++": {
"color": "#f34b7d",
"name": "C++"
},
".hack": {
"color": "#878787",
"name": "Hack"
},
".haml": {
"color": "#ece2a9",
"name": "Haml"
},
".haml.deface": {
"color": "#ece2a9",
"name": "Haml"
},
".handlebars": {
"color": "#f7931e",
"name": "Handlebars"
},
".hats": {
"color": "#1ac620",
"name": "ATS"
},
".hb": {
"color": "#0e60e3",
"name": "Harbour"
},
".hbs": {
"color": "#f7931e",
"name": "Handlebars"
},
".hc": {
"color": "#ffefaf",
"name": "HolyC"
},
".hh": {
"color": "#878787",
"name": "Hack"
},
".hhi": {
"color": "#878787",
"name": "Hack"
},
".hic": {
"color": "#db5855",
"name": "Clojure"
},
".hlsl": {
"color": "#aace60",
"name": "HLSL"
},
".hlsli": {
"color": "#aace60",
"name": "HLSL"
},
".hpp": {
"color": "#f34b7d",
"name": "C++"
},
".hqf": {
"color": "#3F3F3F",
"name": "SQF"
},
".hql": {
"color": "#dce200",
"name": "HiveQL"
},
".hrl": {
"color": "#B83998",
"name": "Erlang"
},
".hs": {
"color": "#5e5086",
"name": "Haskell"
},
".hs-boot": {
"color": "#5e5086",
"name": "Haskell"
},
".hsc": {
"color": "#5e5086",
"name": "Haskell"
},
".hta": {
"color": "#e34c26",
"name": "HTML"
},
".htm": {
"color": "#e34c26",
"name": "HTML"
},
".html": {
"color": "#e34c26",
"name": "HTML"
},
".html.hl": {
"color": "#e34c26",
"name": "HTML"
},
".html.leex": {
"color": "#6e4a7e",
"name": "HTML+EEX"
},
".hx": {
"color": "#df7900",
"name": "Haxe"
},
".hxsl": {
"color": "#df7900",
"name": "Haxe"
},
".hxx": {
"color": "#f34b7d",
"name": "C++"
},
".hy": {
"color": "#7790B2",
"name": "Hy"
},
".i": {
"color": "#005daa",
"name": "Motorola 68K Assembly"
},
".i3": {
"color": "#223388",
"name": "Modula-3"
},
".ice": {
"color": "#003fa2",
"name": "Slice"
},
".iced": {
"color": "#244776",
"name": "CoffeeScript"
},
".icl": {
"color": "#3F85AF",
"name": "Clean"
},
".idc": {
"color": "#555555",
"name": "C"
},
".idr": {
"color": "#b30000",
"name": "Idris"
},
".ig": {
"color": "#223388",
"name": "Modula-3"
},
".ihlp": {
"color": "#1a5f91",
"name": "Stata"
},
".ijm": {
"color": "#99AAFF",
"name": "ImageJ Macro"
},
".ijs": {
"color": "#9EEDFF",
"name": "J"
},
".ik": {
"color": "#078193",
"name": "Ioke"
},
".ily": {
"color": "#9ccc7c",
"name": "LilyPond"
},
".inc": {
"color": "#f69e1d",
"name": "SourcePawn"
},
".inl": {
"color": "#f34b7d",
"name": "C++"
},
".ino": {
"color": "#f34b7d",
"name": "C++"
},
".ins": {
"color": "#3D6117",
"name": "TeX"
},
".intr": {
"color": "#6c616e",
"name": "Dylan"
},
".io": {
"color": "#a9188d",
"name": "Io"
},
".iol": {
"color": "#843179",
"name": "Jolie"
},
".ipf": {
"color": "#0000cc",
"name": "IGOR Pro"
},
".ipp": {
"color": "#f34b7d",
"name": "C++"
},
".ipynb": {
"color": "#DA5B0B",
"name": "Jupyter Notebook"
},
".isl": {
"color": "#264b99",
"name": "Inno Setup"
},
".iss": {
"color": "#264b99",
"name": "Inno Setup"
},
".j": {
"color": "#ff0c5a",
"name": "Objective-J"
},
".j2": {
"color": "#a52a22",
"name": "Jinja"
},
".jade": {
"color": "#a86454",
"name": "Pug"
},
".jake": {
"color": "#f1e05a",
"name": "JavaScript"
},
".jav": {
"color": "#b07219",
"name": "Java"
},
".java": {
"color": "#b07219",
"name": "Java"
},
".javascript": {
"color": "#f1e05a",
"name": "JavaScript"
},
".jbuilder": {
"color": "#701516",
"name": "Ruby"
},
".jflex": {
"color": "#DBCA00",
"name": "JFlex"
},
".jinja": {
"color": "#a52a22",
"name": "Jinja"
},
".jinja2": {
"color": "#a52a22",
"name": "Jinja"
},
".jison": {
"color": "#56b3cb",
"name": "Jison"
},
".jisonlex": {
"color": "#56b3cb",
"name": "Jison Lex"
},
".jl": {
"color": "#a270ba",
"name": "Julia"
},
".jq": {
"color": "#c7254e",
"name": "jq"
},
".js": {
"color": "#f1e05a",
"name": "JavaScript"
},
".js.erb": {
"color": "#f1e05a",
"name": "JavaScript+ERB"
},
".jsb": {
"color": "#f1e05a",
"name": "JavaScript"
},
".jscad": {
"color": "#f1e05a",
"name": "JavaScript"
},
".jsfl": {
"color": "#f1e05a",
"name": "JavaScript"
},
".jsm": {
"color": "#f1e05a",
"name": "JavaScript"
},
".jsonnet": {
"color": "#0064bd",
"name": "Jsonnet"
},
".jsp": {
"color": "#2A6277",
"name": "Java Server Pages"
},
".jss": {
"color": "#f1e05a",
"name": "JavaScript"
},
".jst": {
"color": "#a91e50",
"name": "EJS"
},
".jsx": {
"color": "#f1e05a",
"name": "JavaScript"
},
".kak": {
"color": "#6f8042",
"name": "KakouneScript"
},
".kid": {
"color": "#951531",
"name": "Genshi"
},
".kojo": {
"color": "#c22d40",
"name": "Scala"
},
".krl": {
"color": "#28430A",
"name": "KRL"
},
".ksh": {
"color": "#89e051",
"name": "Shell"
},
".ksy": {
"color": "#773b37",
"name": "Kaitai Struct"
},
".kt": {
"color": "#A97BFF",
"name": "Kotlin"
},
".ktm": {
"color": "#A97BFF",
"name": "Kotlin"
},
".kts": {
"color": "#A97BFF",
"name": "Kotlin"
},
".l": {
"color": "#ecdebe",
"name": "Roff"
},
".lagda": {
"color": "#315665",
"name": "Literate Agda"
},
".las": {
"color": "#999999",
"name": "Lasso"
},
".lasso": {
"color": "#999999",
"name": "Lasso"
},
".lasso8": {
"color": "#999999",
"name": "Lasso"
},
".lasso9": {
"color": "#999999",
"name": "Lasso"
},
".latte": {
"color": "#f2a542",
"name": "Latte"
},
".lbx": {
"color": "#3D6117",
"name": "TeX"
},
".less": {
"color": "#1d365d",
"name": "Less"
},
".lex": {
"color": "#DBCA00",
"name": "Lex"
},
".lfe": {
"color": "#4C3023",
"name": "LFE"
},
".lgt": {
"color": "#295b9a",
"name": "Logtalk"
},
".lhs": {
"color": "#5e5086",
"name": "Literate Haskell"
},
".libsonnet": {
"color": "#0064bd",
"name": "Jsonnet"
},
".lid": {
"color": "#6c616e",
"name": "Dylan"
},
".lidr": {
"color": "#b30000",
"name": "Idris"
},
".linq": {
"color": "#178600",
"name": "C#"
},
".liquid": {
"color": "#67b8de",
"name": "Liquid"
},
".lisp": {
"color": "#87AED7",
"name": "NewLisp"
},
".litcoffee": {
"color": "#244776",
"name": "Literate CoffeeScript"
},
".ll": {
"color": "#185619",
"name": "LLVM"
},
".lmi": {
"color": "#3572A5",
"name": "Python"
},
".logtalk": {
"color": "#295b9a",
"name": "Logtalk"
},
".lol": {
"color": "#cc9900",
"name": "LOLCODE"
},
".lookml": {
"color": "#652B81",
"name": "LookML"
},
".lpr": {
"color": "#E3F171",
"name": "Pascal"
},
".ls": {
"color": "#499886",
"name": "LiveScript"
},
".lsl": {
"color": "#3d9970",
"name": "LSL"
},
".lslp": {
"color": "#3d9970",
"name": "LSL"
},
".lsp": {
"color": "#87AED7",
"name": "NewLisp"
},
".ltx": {
"color": "#3D6117",
"name": "TeX"
},
".lua": {
"color": "#000080",
"name": "Lua"
},
".lvlib": {
"color": "#fede06",
"name": "LabVIEW"
},
".lvproj": {
"color": "#fede06",
"name": "LabVIEW"
},
".ly": {
"color": "#9ccc7c",
"name": "LilyPond"
},
".m": {
"color": "#438eff",
"name": "Objective-C"
},
".m2": {
"color": "#d8ffff",
"name": "Macaulay2"
},
".m3": {
"color": "#223388",
"name": "Modula-3"
},
".ma": {
"color": "#dd1100",
"name": "Mathematica"
},
".mak": {
"color": "#427819",
"name": "Makefile"
},
".make": {
"color": "#427819",
"name": "Makefile"
},
".makefile": {
"color": "#427819",
"name": "Makefile"
},
".mako": {
"color": "#7e858d",
"name": "Mako"
},
".man": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".mao": {
"color": "#7e858d",
"name": "Mako"
},
".marko": {
"color": "#42bff2",
"name": "Marko"
},
".mask": {
"color": "#f97732",
"name": "Mask"
},
".mata": {
"color": "#1a5f91",
"name": "Stata"
},
".matah": {
"color": "#1a5f91",
"name": "Stata"
},
".mathematica": {
"color": "#dd1100",
"name": "Mathematica"
},
".matlab": {
"color": "#e16737",
"name": "MATLAB"
},
".mawk": {
"color": "#c30e9b",
"name": "Awk"
},
".maxhelp": {
"color": "#c4a79c",
"name": "Max"
},
".maxpat": {
"color": "#c4a79c",
"name": "Max"
},
".maxproj": {
"color": "#c4a79c",
"name": "Max"
},
".mcfunction": {
"color": "#E22837",
"name": "mcfunction"
},
".mcr": {
"color": "#00a6a6",
"name": "MAXScript"
},
".mdoc": {
"color": "#ecdebe",
"name": "Roff Manpage"
},
".me": {
"color": "#ecdebe",
"name": "Roff"
},
".metal": {
"color": "#8f14e9",
"name": "Metal"
},
".mg": {
"color": "#223388",
"name": "Modula-3"
},
".mirah": {
"color": "#c7a938",
"name": "Mirah"
},
".mjs": {
"color": "#f1e05a",
"name": "JavaScript"
},
".mk": {
"color": "#427819",
"name": "Makefile"
},
".mkfile": {
"color": "#427819",
"name": "Makefile"
},
".mkii": {
"color": "#3D6117",
"name": "TeX"
},
".mkiv": {
"color": "#3D6117",
"name": "TeX"
},
".mkvi": {
"color": "#3D6117",
"name": "TeX"
},
".ml": {
"color": "#dc566d",
"name": "Standard ML"
},
".ml4": {
"color": "#3be133",
"name": "OCaml"
},
".mli": {
"color": "#3be133",
"name": "OCaml"
},
".mlir": {
"color": "#5EC8DB",
"name": "MLIR"
},
".mll": {
"color": "#3be133",
"name": "OCaml"
},
".mly": {
"color": "#3be133",
"name": "OCaml"
},
".mm": {
"color": "#6866fb",
"name": "Objective-C++"
},
".mo": {
"color": "#de1d31",
"name": "Modelica"
},
".mod": {
"color": "#10253f",
"name": "Modula-2"
},
".model.lkml": {
"color": "#652B81",
"name": "LookML"
},
".moo": {
"color": "#ff2b2b",
"name": "Mercury"
},
".moon": {
"color": "#ff4585",
"name": "MoonScript"
},
".mq4": {
"color": "#62A8D6",
"name": "MQL4"
},
".mq5": {
"color": "#4A76B8",
"name": "MQL5"
},
".mqh": {
"color": "#4A76B8",
"name": "MQL5"
},
".mrc": {
"color": "#3d57c3",
"name": "mIRC Script"
},
".ms": {
"color": "#ecdebe",
"name": "Roff"
},
".mspec": {
"color": "#701516",
"name": "Ruby"
},
".mt": {
"color": "#dd1100",
"name": "Mathematica"
},
".mtml": {
"color": "#b7e1f4",
"name": "MTML"
},
".mu": {
"color": "#244963",
"name": "mupad"
},
".mud": {
"color": "#dc75e5",
"name": "ZIL"
},
".mustache": {
"color": "#724b3b",
"name": "Mustache"
},
".mxt": {
"color": "#c4a79c",
"name": "Max"
},
".n": {
"color": "#ecdebe",
"name": "Roff"
},
".nasm": {
"color": "#6E4C13",
"name": "Assembly"
},
".nawk": {
"color": "#c30e9b",
"name": "Awk"
},
".nb": {
"color": "#dd1100",
"name": "Mathematica"
},
".nbp": {
"color": "#dd1100",
"name": "Mathematica"
},
".nc": {
"color": "#94B0C7",
"name": "nesC"
},
".ncl": {
"color": "#28431f",
"name": "NCL"
},
".ne": {
"color": "#990000",
"name": "Nearley"
},
".nearley": {
"color": "#990000",
"name": "Nearley"
},
".nf": {
"color": "#3ac486",
"name": "Nextflow"
},
".nim": {
"color": "#ffc200",
"name": "Nim"
},
".nim.cfg": {
"color": "#ffc200",
"name": "Nim"
},
".nimble": {
"color": "#ffc200",
"name": "Nim"
},
".nimrod": {
"color": "#ffc200",
"name": "Nim"
},
".nims": {
"color": "#ffc200",
"name": "Nim"
},
".nit": {
"color": "#009917",
"name": "Nit"
},
".nix": {
"color": "#7e7eff",
"name": "Nix"
},
".njk": {
"color": "#3d8137",
"name": "Nunjucks"
},
".njs": {
"color": "#f1e05a",
"name": "JavaScript"
},
".nl": {
"color": "#87AED7",
"name": "NewLisp"
},
".nlogo": {
"color": "#ff6375",
"name": "NetLogo"
},
".nqp": {
"color": "#0000fb",
"name": "Raku"
},
".nr": {
"color": "#ecdebe",
"name": "Roff"
},
".nse": {
"color": "#000080",
"name": "Lua"
},
".nss": {
"color": "#111522",
"name": "NWScript"
},
".nu": {
"color": "#c9df40",
"name": "Nu"
},
".numpy": {
"color": "#9C8AF9",
"name": "NumPy"
},
".numpyw": {
"color": "#9C8AF9",
"name": "NumPy"
},
".numsc": {
"color": "#9C8AF9",
"name": "NumPy"
},
".nut": {
"color": "#800000",
"name": "Squirrel"
},
".ny": {
"color": "#3fb68b",
"name": "Common Lisp"
},
".odin": {
"color": "#60AFFE",
"name": "Odin"
},
".ol": {
"color": "#843179",
"name": "Jolie"
},
".omgrofl": {
"color": "#cabbff",
"name": "Omgrofl"
},
".ooc": {
"color": "#b0b77e",
"name": "ooc"
},
".opal": {
"color": "#f7ede0",
"name": "Opal"
},
".opencl": {
"color": "#ed2e2d",
"name": "OpenCL"
},
".orc": {
"color": "#1a1a1a",
"name": "Csound"
},
".os": {
"color": "#814CCC",
"name": "1C Enterprise"
},
".oxygene": {
"color": "#cdd0e3",
"name": "Oxygene"
},
".oz": {
"color": "#fab738",
"name": "Oz"
},
".p": {
"color": "#5ce600",
"name": "OpenEdge ABL"
},
".p4": {
"color": "#7055b5",
"name": "P4"
},
".p6": {
"color": "#0000fb",
"name": "Raku"
},
".p6l": {
"color": "#0000fb",
"name": "Raku"
},
".p6m": {
"color": "#0000fb",
"name": "Raku"
},
".p8": {
"color": "#000080",
"name": "Lua"
},
".pac": {
"color": "#f1e05a",
"name": "JavaScript"
},
".pan": {
"color": "#cc0000",
"name": "Pan"
},
".parrot": {
"color": "#f3ca0a",
"name": "Parrot"
},
".pas": {
"color": "#E3F171",
"name": "Pascal"
},
".pascal": {
"color": "#E3F171",
"name": "Pascal"
},
".pat": {
"color": "#c4a79c",
"name": "Max"
},
".pb": {
"color": "#5a6986",
"name": "PureBasic"
},
".pbi": {
"color": "#5a6986",
"name": "PureBasic"
},
".pbt": {
"color": "#8f0f8d",
"name": "PowerBuilder"
},
".pck": {
"color": "#dad8d8",
"name": "PLSQL"
},
".pcss": {
"color": "#dc3a0c",
"name": "PostCSS"
},
".pd_lua": {
"color": "#000080",
"name": "Lua"
},
".pde": {
"color": "#0096D8",
"name": "Processing"
},
".pegjs": {
"color": "#234d6b",
"name": "PEG.js"
},
".pep": {
"color": "#C76F5B",
"name": "Pep8"
},
".perl": {
"color": "#0298c3",
"name": "Perl"
},
".pfa": {
"color": "#da291c",
"name": "PostScript"
},
".pgsql": {
"color": "#336790",
"name": "PLpgSQL"
},
".ph": {
"color": "#0298c3",
"name": "Perl"
},
".php": {
"color": "#4F5D95",
"name": "PHP"
},
".php3": {
"color": "#4F5D95",
"name": "PHP"
},
".php4": {
"color": "#4F5D95",
"name": "PHP"
},
".php5": {
"color": "#4F5D95",
"name": "PHP"
},
".phps": {
"color": "#4F5D95",
"name": "PHP"
},
".phpt": {
"color": "#4F5D95",
"name": "PHP"
},
".phtml": {
"color": "#4f5d95",
"name": "HTML+PHP"
},
".pig": {
"color": "#fcd7de",
"name": "PigLatin"
},
".pike": {
"color": "#005390",
"name": "Pike"
},
".pkb": {
"color": "#dad8d8",
"name": "PLSQL"
},
".pks": {
"color": "#dad8d8",
"name": "PLSQL"
},
".pl": {
"color": "#0000fb",
"name": "Raku"
},
".pl6": {
"color": "#0000fb",
"name": "Raku"
},
".plb": {
"color": "#dad8d8",
"name": "PLSQL"
},
".plot": {
"color": "#f0a9f0",
"name": "Gnuplot"
},
".pls": {
"color": "#dad8d8",
"name": "PLSQL"
},
".plsql": {
"color": "#dad8d8",
"name": "PLSQL"
},
".plt": {
"color": "#f0a9f0",
"name": "Gnuplot"
},
".pluginspec": {
"color": "#701516",
"name": "Ruby"
},
".plx": {
"color": "#0298c3",
"name": "Perl"
},
".pm": {
"color": "#0000fb",
"name": "Raku"
},
".pm6": {
"color": "#0000fb",
"name": "Raku"
},
".pmod": {
"color": "#005390",
"name": "Pike"
},
".podsl": {
"color": "#3fb68b",
"name": "Common Lisp"
},
".podspec": {
"color": "#701516",
"name": "Ruby"
},
".pogo": {
"color": "#d80074",
"name": "PogoScript"
},
".postcss": {
"color": "#dc3a0c",
"name": "PostCSS"
},
".pov": {
"color": "#6bac65",
"name": "POV-Ray SDL"
},
".pp": {
"color": "#302B6D",
"name": "Puppet"
},
".pprx": {
"color": "#d90e09",
"name": "REXX"
},
".prawn": {
"color": "#701516",
"name": "Ruby"
},
".prc": {
"color": "#dad8d8",
"name": "PLSQL"
},
".prg": {
"color": "#403a40",
"name": "xBase"
},
".pro": {
"color": "#74283c",
"name": "Prolog"
},
".prolog": {
"color": "#74283c",
"name": "Prolog"
},
".prw": {
"color": "#403a40",
"name": "xBase"
},
".ps": {
"color": "#da291c",
"name": "PostScript"
},
".ps1": {
"color": "#012456",
"name": "PowerShell"
},
".psc": {
"color": "#6600cc",
"name": "Papyrus"
},
".psd1": {
"color": "#012456",
"name": "PowerShell"
},
".psgi": {
"color": "#0298c3",
"name": "Perl"
},
".psm1": {
"color": "#012456",
"name": "PowerShell"
},
".pug": {
"color": "#a86454",
"name": "Pug"
},
".purs": {
"color": "#1D222D",
"name": "PureScript"
},
".pwn": {
"color": "#dbb284",
"name": "Pawn"
},
".pxd": {
"color": "#fedf5b",
"name": "Cython"
},
".pxi": {
"color": "#fedf5b",
"name": "Cython"
},
".py": {
"color": "#3572A5",
"name": "Python"
},
".py3": {
"color": "#3572A5",
"name": "Python"
},
".pyde": {
"color": "#3572A5",
"name": "Python"
},
".pyi": {
"color": "#3572A5",
"name": "Python"
},
".pyp": {
"color": "#3572A5",
"name": "Python"
},
".pyt": {
"color": "#3572A5",
"name": "Python"
},
".pyw": {
"color": "#3572A5",
"name": "Python"
},
".pyx": {
"color": "#fedf5b",
"name": "Cython"
},
".q": {
"color": "#0040cd",
"name": "q"
},
".qasm": {
"color": "#AA70FF",
"name": "OpenQASM"
},
".qbs": {
"color": "#44a51c",
"name": "QML"
},
".ql": {
"color": "#140f46",
"name": "CodeQL"
},
".qll": {
"color": "#140f46",
"name": "CodeQL"
},
".qml": {
"color": "#44a51c",
"name": "QML"
},
".qs": {
"color": "#00b841",
"name": "Qt Script"
},
".r": {
"color": "#358a5b",
"name": "Rebol"
},
".r2": {
"color": "#358a5b",
"name": "Rebol"
},
".r3": {
"color": "#358a5b",
"name": "Rebol"
},
".rabl": {
"color": "#701516",
"name": "Ruby"
},
".rake": {
"color": "#701516",
"name": "Ruby"
},
".raku": {
"color": "#0000fb",
"name": "Raku"
},
".rakumod": {
"color": "#0000fb",
"name": "Raku"
},
".raml": {
"color": "#77d9fb",
"name": "RAML"
},
".razor": {
"color": "#512be4",
"name": "HTML+Razor"
},
".rb": {
"color": "#701516",
"name": "Ruby"
},
".rbi": {
"color": "#701516",
"name": "Ruby"
},
".rbuild": {
"color": "#701516",
"name": "Ruby"
},
".rbw": {
"color": "#701516",
"name": "Ruby"
},
".rbx": {
"color": "#701516",
"name": "Ruby"
},
".rbxs": {
"color": "#000080",
"name": "Lua"
},
".rd": {
"color": "#198CE7",
"name": "R"
},
".re": {
"color": "#ff5847",
"name": "Reason"
},
".reb": {
"color": "#358a5b",
"name": "Rebol"
},
".rebol": {
"color": "#358a5b",
"name": "Rebol"
},
".red": {
"color": "#f50000",
"name": "Red"
},
".reds": {
"color": "#f50000",
"name": "Red"
},
".rego": {
"color": "#7d9199",
"name": "Open Policy Agent"
},
".rei": {
"color": "#ff5847",
"name": "Reason"
},
".res": {
"color": "#ed5051",
"name": "ReScript"
},
".rex": {
"color": "#d90e09",
"name": "REXX"
},
".rexx": {
"color": "#d90e09",
"name": "REXX"
},
".rg": {
"color": "#cc0088",
"name": "Rouge"
},
".rhtml": {
"color": "#701516",
"name": "HTML+ERB"
},
".ring": {
"color": "#2D54CB",
"name": "Ring"
},
".riot": {
"color": "#A71E49",
"name": "Riot"
},
".rkt": {
"color": "#3c5caa",
"name": "Racket"
},
".rktd": {
"color": "#3c5caa",
"name": "Racket"
},
".rktl": {
"color": "#3c5caa",
"name": "Racket"
},
".rl": {
"color": "#9d5200",
"name": "Ragel"
},
".rnh": {
"color": "#665a4e",
"name": "RUNOFF"
},
".rno": {
"color": "#ecdebe",
"name": "Roff"
},
".robot": {
"color": "#00c0b5",
"name": "RobotFramework"
},
".rockspec": {
"color": "#000080",
"name": "Lua"
},
".roff": {
"color": "#ecdebe",
"name": "Roff"
},
".rpy": {
"color": "#ff7f7f",
"name": "Ren'Py"
},
".rs": {
"color": "#dea584",
"name": "Rust"
},
".rs.in": {
"color": "#dea584",
"name": "Rust"
},
".rsc": {
"color": "#fffaa0",
"name": "Rascal"
},
".rsx": {
"color": "#198CE7",
"name": "R"
},
".ru": {
"color": "#701516",
"name": "Ruby"
},
".ruby": {
"color": "#701516",
"name": "Ruby"
},
".s": {
"color": "#005daa",
"name": "Motorola 68K Assembly"
},
".sas": {
"color": "#B34936",
"name": "SAS"
},
".sass": {
"color": "#a53b70",
"name": "Sass"
},
".sats": {
"color": "#1ac620",
"name": "ATS"
},
".sbt": {
"color": "#c22d40",
"name": "Scala"
},
".sc": {
"color": "#46390b",
"name": "SuperCollider"
},
".scad": {
"color": "#e5cd45",
"name": "OpenSCAD"
},
".scala": {
"color": "#c22d40",
"name": "Scala"
},
".scaml": {
"color": "#bd181a",
"name": "Scaml"
},
".scd": {
"color": "#46390b",
"name": "SuperCollider"
},
".sce": {
"color": "#ca0f21",
"name": "Scilab"
},
".sch": {
"color": "#1e4aec",
"name": "Scheme"
},
".sci": {
"color": "#ca0f21",
"name": "Scilab"
},
".scm": {
"color": "#1e4aec",
"name": "Scheme"
},
".sco": {
"color": "#1a1a1a",
"name": "Csound Score"
},
".scpt": {
"color": "#101F1F",
"name": "AppleScript"
},
".scrbl": {
"color": "#3c5caa",
"name": "Racket"
},
".scss": {
"color": "#c6538c",
"name": "SCSS"
},
".sed": {
"color": "#64b970",
"name": "sed"
},
".self": {
"color": "#0579aa",
"name": "Self"
},
".sexp": {
"color": "#3fb68b",
"name": "Common Lisp"
},
".sh": {
"color": "#89e051",
"name": "Shell"
},
".sh.in": {
"color": "#89e051",
"name": "Shell"
},
".shader": {
"color": "#222c37",
"name": "ShaderLab"
},
".shen": {
"color": "#120F14",
"name": "Shen"
},
".sig": {
"color": "#dc566d",
"name": "Standard ML"
},
".sj": {
"color": "#ff0c5a",
"name": "Objective-J"
},
".sjs": {
"color": "#f1e05a",
"name": "JavaScript"
},
".sl": {
"color": "#007eff",
"name": "Slash"
},
".sld": {
"color": "#1e4aec",
"name": "Scheme"
},
".slim": {
"color": "#2b2b2b",
"name": "Slim"
},
".sls": {
"color": "#1e4aec",
"name": "Scheme"
},
".sma": {
"color": "#dbb284",
"name": "Pawn"
},
".smk": {
"color": "#3572A5",
"name": "Python"
},
".sml": {
"color": "#dc566d",
"name": "Standard ML"
},
".snip": {
"color": "#199f4b",
"name": "Vim Snippet"
},
".snippet": {
"color": "#199f4b",
"name": "Vim Snippet"
},
".snippets": {
"color": "#199f4b",
"name": "Vim Snippet"
},
".sol": {
"color": "#AA6746",
"name": "Solidity"
},
".soy": {
"color": "#0d948f",
"name": "Closure Templates"
},
".sp": {
"color": "#f69e1d",
"name": "SourcePawn"
},
".spc": {
"color": "#dad8d8",
"name": "PLSQL"
},
".spec": {
"color": "#701516",
"name": "Ruby"
},
".spin": {
"color": "#7fa2a7",
"name": "Propeller Spin"
},
".sps": {
"color": "#1e4aec",
"name": "Scheme"
},
".sqf": {
"color": "#3F3F3F",
"name": "SQF"
},
".sql": {
"color": "#e38c00",
"name": "TSQL"
},
".sra": {
"color": "#8f0f8d",
"name": "PowerBuilder"
},
".srt": {
"color": "#348a34",
"name": "SRecode Template"
},
".sru": {
"color": "#8f0f8d",
"name": "PowerBuilder"
},
".srw": {
"color": "#8f0f8d",
"name": "PowerBuilder"
},
".ss": {
"color": "#1e4aec",
"name": "Scheme"
},
".ssjs": {
"color": "#f1e05a",
"name": "JavaScript"
},
".sss": {
"color": "#2fcc9f",
"name": "SugarSS"
},
".st": {
"color": "#3fb34f",
"name": "StringTemplate"
},
".stan": {
"color": "#b2011d",
"name": "Stan"
},
".sthlp": {
"color": "#1a5f91",
"name": "Stata"
},
".story": {
"color": "#5B2063",
"name": "Gherkin"
},
".sty": {
"color": "#3D6117",
"name": "TeX"
},
".styl": {
"color": "#ff6347",
"name": "Stylus"
},
".sv": {
"color": "#DAE1C2",
"name": "SystemVerilog"
},
".svelte": {
"color": "#ff3e00",
"name": "Svelte"
},
".svh": {
"color": "#DAE1C2",
"name": "SystemVerilog"
},
".swift": {
"color": "#F05138",
"name": "Swift"
},
".t": {
"color": "#cf142b",
"name": "Turing"
},
".tac": {
"color": "#3572A5",
"name": "Python"
},
".tcc": {
"color": "#f34b7d",
"name": "C++"
},
".tcl": {
"color": "#e4cc98",
"name": "Tcl"
},
".tcl.in": {
"color": "#e4cc98",
"name": "Tcl"
},
".tesc": {
"color": "#5686a5",
"name": "GLSL"
},
".tese": {
"color": "#5686a5",
"name": "GLSL"
},
".tex": {
"color": "#3D6117",
"name": "TeX"
},
".thor": {
"color": "#701516",
"name": "Ruby"
},
".thrift": {
"color": "#D12127",
"name": "Thrift"
},
".thy": {
"color": "#FEFE00",
"name": "Isabelle"
},
".tla": {
"color": "#4b0079",
"name": "TLA"
},
".tm": {
"color": "#e4cc98",
"name": "Tcl"
},
".tmac": {
"color": "#ecdebe",
"name": "Roff"
},
".tmux": {
"color": "#89e051",
"name": "Shell"
},
".toc": {
"color": "#3D6117",
"name": "TeX"
},
".tool": {
"color": "#89e051",
"name": "Shell"
},
".tpb": {
"color": "#dad8d8",
"name": "PLSQL"
},
".tpl": {
"color": "#f0c040",
"name": "Smarty"
},
".tpp": {
"color": "#f34b7d",
"name": "C++"
},
".tps": {
"color": "#dad8d8",
"name": "PLSQL"
},
".trg": {
"color": "#dad8d8",
"name": "PLSQL"
},
".ts": {
"color": "#2b7489",
"name": "TypeScript"
},
".tst": {
"color": "#ca0f21",
"name": "Scilab"
},
".tsx": {
"color": "#2b7489",
"name": "TypeScript"
},
".tu": {
"color": "#cf142b",
"name": "Turing"
},
".twig": {
"color": "#c1d026",
"name": "Twig"
},
".txl": {
"color": "#0178b8",
"name": "TXL"
},
".uc": {
"color": "#a54c4d",
"name": "UnrealScript"
},
".udo": {
"color": "#1a1a1a",
"name": "Csound"
},
".uno": {
"color": "#9933cc",
"name": "Uno"
},
".upc": {
"color": "#4e3617",
"name": "Unified Parallel C"
},
".ur": {
"color": "#ccccee",
"name": "UrWeb"
},
".urs": {
"color": "#ccccee",
"name": "UrWeb"
},
".v": {
"color": "#b2b7f8",
"name": "Verilog"
},
".vala": {
"color": "#fbe5cd",
"name": "Vala"
},
".vapi": {
"color": "#fbe5cd",
"name": "Vala"
},
".vark": {
"color": "#82937f",
"name": "Gosu"
},
".vb": {
"color": "#945db7",
"name": "Visual Basic .NET"
},
".vba": {
"color": "#199f4b",
"name": "Vim Script"
},
".vbhtml": {
"color": "#945db7",
"name": "Visual Basic .NET"
},
".vbs": {
"color": "#15dcdc",
"name": "VBScript"
},
".vcl": {
"color": "#148AA8",
"name": "VCL"
},
".veo": {
"color": "#b2b7f8",
"name": "Verilog"
},
".vert": {
"color": "#5686a5",
"name": "GLSL"
},
".vh": {
"color": "#DAE1C2",
"name": "SystemVerilog"
},
".vhd": {
"color": "#adb2cb",
"name": "VHDL"
},
".vhdl": {
"color": "#adb2cb",
"name": "VHDL"
},
".vhf": {
"color": "#adb2cb",
"name": "VHDL"
},
".vhi": {
"color": "#adb2cb",
"name": "VHDL"
},
".vho": {
"color": "#adb2cb",
"name": "VHDL"
},
".vhs": {
"color": "#adb2cb",
"name": "VHDL"
},
".vht": {
"color": "#adb2cb",
"name": "VHDL"
},
".vhw": {
"color": "#adb2cb",
"name": "VHDL"
},
".view.lkml": {
"color": "#652B81",
"name": "LookML"
},
".vim": {
"color": "#199f4b",
"name": "Vim Script"
},
".vmb": {
"color": "#199f4b",
"name": "Vim Script"
},
".volt": {
"color": "#1F1F1F",
"name": "Volt"
},
".vrx": {
"color": "#5686a5",
"name": "GLSL"
},
".vsh": {
"color": "#5686a5",
"name": "GLSL"
},
".vshader": {
"color": "#5686a5",
"name": "GLSL"
},
".vue": {
"color": "#41b883",
"name": "Vue"
},
".vw": {
"color": "#dad8d8",
"name": "PLSQL"
},
".w": {
"color": "#5ce600",
"name": "OpenEdge ABL"
},
".wast": {
"color": "#04133b",
"name": "WebAssembly"
},
".wat": {
"color": "#04133b",
"name": "WebAssembly"
},
".watchr": {
"color": "#701516",
"name": "Ruby"
},
".wdl": {
"color": "#42f1f4",
"name": "wdl"
},
".wisp": {
"color": "#7582D1",
"name": "wisp"
},
".wl": {
"color": "#dd1100",
"name": "Mathematica"
},
".wlk": {
"color": "#a23738",
"name": "Wollok"
},
".wlt": {
"color": "#dd1100",
"name": "Mathematica"
},
".wlua": {
"color": "#000080",
"name": "Lua"
},
".wsgi": {
"color": "#3572A5",
"name": "Python"
},
".x10": {
"color": "#4B6BEF",
"name": "X10"
},
".x68": {
"color": "#005daa",
"name": "Motorola 68K Assembly"
},
".xc": {
"color": "#99DA07",
"name": "XC"
},
".xht": {
"color": "#e34c26",
"name": "HTML"
},
".xhtml": {
"color": "#e34c26",
"name": "HTML"
},
".xojo_code": {
"color": "#81bd41",
"name": "Xojo"
},
".xojo_menu": {
"color": "#81bd41",
"name": "Xojo"
},
".xojo_report": {
"color": "#81bd41",
"name": "Xojo"
},
".xojo_script": {
"color": "#81bd41",
"name": "Xojo"
},
".xojo_toolbar": {
"color": "#81bd41",
"name": "Xojo"
},
".xojo_window": {
"color": "#81bd41",
"name": "Xojo"
},
".xpy": {
"color": "#3572A5",
"name": "Python"
},
".xq": {
"color": "#5232e7",
"name": "XQuery"
},
".xql": {
"color": "#5232e7",
"name": "XQuery"
},
".xqm": {
"color": "#5232e7",
"name": "XQuery"
},
".xquery": {
"color": "#5232e7",
"name": "XQuery"
},
".xqy": {
"color": "#5232e7",
"name": "XQuery"
},
".xrl": {
"color": "#B83998",
"name": "Erlang"
},
".xsh": {
"color": "#285EEF",
"name": "Xonsh"
},
".xsjs": {
"color": "#f1e05a",
"name": "JavaScript"
},
".xsjslib": {
"color": "#f1e05a",
"name": "JavaScript"
},
".xsl": {
"color": "#EB8CEB",
"name": "XSLT"
},
".xslt": {
"color": "#EB8CEB",
"name": "XSLT"
},
".xtend": {
"color": "#24255d",
"name": "Xtend"
},
".xzap": {
"color": "#0d665e",
"name": "ZAP"
},
".y": {
"color": "#4B6C4B",
"name": "Yacc"
},
".yacc": {
"color": "#4B6C4B",
"name": "Yacc"
},
".yap": {
"color": "#74283c",
"name": "Prolog"
},
".yar": {
"color": "#220000",
"name": "YARA"
},
".yara": {
"color": "#220000",
"name": "YARA"
},
".yasnippet": {
"color": "#32AB90",
"name": "YASnippet"
},
".yrl": {
"color": "#B83998",
"name": "Erlang"
},
".yy": {
"color": "#4B6C4B",
"name": "Yacc"
},
".zap": {
"color": "#0d665e",
"name": "ZAP"
},
".zep": {
"color": "#118f9e",
"name": "Zephir"
},
".zig": {
"color": "#ec915c",
"name": "Zig"
},
".zil": {
"color": "#dc75e5",
"name": "ZIL"
},
".zimpl": {
"color": "#d67711",
"name": "Zimpl"
},
".zmpl": {
"color": "#d67711",
"name": "Zimpl"
},
".zpl": {
"color": "#d67711",
"name": "Zimpl"
},
".zs": {
"color": "#00BCD1",
"name": "ZenScript"
},
".zsh": {
"color": "#89e051",
"name": "Shell"
}
}
================================================
FILE: backend/src/data/github/graphql/__init__.py
================================================
from src.data.github.graphql.commit import get_commits
from src.data.github.graphql.models import RawCommit, RawRepo
from src.data.github.graphql.repo import get_repo
from src.data.github.graphql.template import (
GraphQLErrorMissingNode,
GraphQLErrorRateLimit,
GraphQLErrorTimeout,
get_query_limit,
)
from src.data.github.graphql.user.contribs.contribs import (
get_user_contribution_calendar,
get_user_contribution_events,
)
from src.data.github.graphql.user.contribs.models import (
RawCalendar,
RawEvents,
RawEventsCommit,
RawEventsEvent,
)
from src.data.github.graphql.user.follows.follows import (
get_user_followers,
get_user_following,
)
from src.data.github.graphql.user.follows.models import RawFollows
__all__ = [
"get_commits",
"RawCommit",
"RawRepo",
"get_repo",
"GraphQLErrorMissingNode",
"GraphQLErrorRateLimit",
"GraphQLErrorTimeout",
"get_query_limit",
"get_user_contribution_calendar",
"get_user_contribution_events",
"RawCalendar",
"RawEvents",
"RawEventsCommit",
"RawEventsEvent",
"get_user_followers",
"get_user_following",
"RawFollows",
]
================================================
FILE: backend/src/data/github/graphql/commit.py
================================================
from typing import List, Optional
from src.constants import PR_FILES
from src.data.github.graphql.models import RawCommit
from src.data.github.graphql.template import (
GraphQLError,
GraphQLErrorMissingNode,
GraphQLErrorRateLimit,
GraphQLErrorTimeout,
get_template,
)
def get_commits(
node_ids: List[str], access_token: Optional[str] = None, catch_errors: bool = False
) -> List[Optional[RawCommit]]:
"""
Gets all repository data from graphql
:param access_token: GitHub access token
:param node_ids: List of node ids
:return: List of commits
"""
if PR_FILES == 0: # type: ignore
query = {
"variables": {"ids": node_ids},
"query": """
query getCommits($ids: [ID!]!) {
nodes(ids: $ids) {
... on Commit {
additions
deletions
changedFiles
url
}
}
}
""",
}
else:
query = {
"variables": {"ids": node_ids, "first": PR_FILES},
"query": """
query getCommits($ids: [ID!]!, $first: Int!) {
nodes(ids: $ids) {
... on Commit {
additions
deletions
changedFiles
url
associatedPullRequests(first: 1) {
nodes {
changedFiles
additions
deletions
files(first: $first) {
nodes {
path
additions
deletions
}
}
}
}
}
}
}
""",
}
try:
raw_commits = get_template(query, access_token)["data"]["nodes"]
except GraphQLErrorMissingNode as e:
return (
get_commits(node_ids[: e.node], access_token)
+ [None]
+ get_commits(node_ids[e.node + 1 :], access_token)
)
except (GraphQLErrorRateLimit, GraphQLErrorTimeout, GraphQLError) as e:
if catch_errors:
return [None for _ in node_ids]
raise e
out: List[Optional[RawCommit]] = []
for raw_commit in raw_commits:
try:
if "associatedPullRequests" not in raw_commit:
raw_commit["associatedPullRequests"] = {"nodes": []}
out.append(RawCommit.model_validate(raw_commit))
except Exception as e:
if catch_errors:
out.append(None)
else:
raise e
return out
================================================
FILE: backend/src/data/github/graphql/models.py
================================================
from typing import List, Optional
from pydantic import BaseModel, Field
class RawCommitPRFileNode(BaseModel):
path: str
additions: int
deletions: int
class RawCommitPRFile(BaseModel):
nodes: List[RawCommitPRFileNode]
class RawCommitPRNode(BaseModel):
changed_files: int = Field(alias="changedFiles")
additions: int
deletions: int
files: RawCommitPRFile
class RawCommitPR(BaseModel):
nodes: List[RawCommitPRNode]
class RawCommit(BaseModel):
additions: int
deletions: int
changed_files: int = Field(alias="changedFiles")
url: str
prs: RawCommitPR = Field(alias="associatedPullRequests")
class RawRepoLanguageNode(BaseModel):
name: str
color: Optional[str]
class RawRepoLanguageEdge(BaseModel):
node: RawRepoLanguageNode
size: int
class RawRepoLanguage(BaseModel):
total_count: int = Field(alias="totalCount")
total_size: int = Field(alias="totalSize")
edges: List[RawRepoLanguageEdge]
class RawRepo(BaseModel):
is_private: bool = Field(alias="isPrivate")
fork_count: int = Field(alias="forkCount")
stargazer_count: int = Field(alias="stargazerCount")
languages: RawRepoLanguage
================================================
FILE: backend/src/data/github/graphql/repo.py
================================================
from typing import Optional
from src.data.github.graphql.models import RawRepo
from src.data.github.graphql.template import get_template
def get_repo(
owner: str,
repo: str,
access_token: Optional[str] = None,
catch_errors: bool = False,
) -> Optional[RawRepo]:
"""
Gets all repository data from graphql
:param access_token: GitHub access token
:param owner: GitHub owner
:param repo: GitHub repository
:return: RawRepo object or None if repo not present
"""
query = {
"variables": {"owner": owner, "repo": repo},
"query": """
query getRepo($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
isPrivate,
forkCount,
stargazerCount,
languages(first: 10){
totalCount,
totalSize,
edges{
node {
name,
color,
},
size,
},
},
}
}
""",
}
try:
raw_repo = get_template(query, access_token)["data"]["repository"]
return RawRepo.model_validate(raw_repo)
except Exception as e:
if catch_errors:
return None
raise e
================================================
FILE: backend/src/data/github/graphql/template.py
================================================
import logging
from datetime import datetime
from typing import Any, Dict, Optional, Tuple
import requests
from requests.exceptions import ReadTimeout
from src.constants import TIMEOUT
from src.data.github.utils import get_access_token
s = requests.session()
class GraphQLError(Exception):
pass
class GraphQLErrorMissingNode(Exception):
def __init__(self, node: int, *args: Tuple[Any], **kwargs: Dict[str, Any]):
super(Exception, self).__init__(*args, **kwargs)
self.node = node
class GraphQLErrorRateLimit(Exception):
pass
class GraphQLErrorTimeout(Exception):
pass
def get_template(
query: Dict[str, Any], access_token: Optional[str] = None, retries: int = 0
) -> Dict[str, Any]:
"""
Template for interacting with the GitHub GraphQL API
:param query: The query to be sent to the GitHub GraphQL API
:param access_token: The access token to be used for the query
:param retries: The number of retries to be made for Auth Exceptions
:return: The response from the GitHub GraphQL API
"""
start = datetime.now()
new_access_token = get_access_token(access_token)
headers: Dict[str, str] = {"Authorization": f"bearer {new_access_token}"}
try:
r = s.post(
"https://api.github.com/graphql",
json=query,
headers=headers,
timeout=TIMEOUT,
)
except ReadTimeout:
raise GraphQLErrorTimeout("GraphQL Error: Request Timeout")
print("GraphQL", new_access_token, datetime.now() - start)
if r.status_code == 200:
data = r.json()
if "errors" in data:
if (
"type" in data["errors"][0]
and data["errors"][0]["type"] in ["SERVICE_UNAVAILABLE", "NOT_FOUND"]
and "path" in data["errors"][0]
and isinstance(data["errors"][0]["path"], list)
and data["errors"][0]["path"][0] == "nodes"
):
raise GraphQLErrorMissingNode(node=int(data["errors"][0]["path"][1]))
if retries < 2:
print("GraphQL Error, Retrying:", new_access_token)
return get_template(query, access_token, retries + 1)
raise GraphQLError("GraphQL Error: " + str(data["errors"]))
return data
if r.status_code in [401, 403]:
if retries < 2:
print("GraphQL Error, Retrying:", new_access_token)
return get_template(query, access_token, retries + 1)
raise GraphQLErrorRateLimit("GraphQL Error: Unauthorized")
if r.status_code == 502:
raise GraphQLErrorTimeout("GraphQL Error: Request Timeout")
raise GraphQLError(f"GraphQL Error: {str(r.status_code)}")
def get_query_limit(access_token: str) -> int:
"""
Get the current rate limit for the GitHub GraphQL API
:param access_token: The access token to be used for the query
:return: The current rate limit for the GitHub GraphQL API
"""
try:
data = get_template(
{"query": "query { rateLimit { remaining } }"}, access_token
)
return data["data"]["rateLimit"]["remaining"]
except Exception as e:
logging.exception(e)
return -1
================================================
FILE: backend/src/data/github/graphql/user/__init__.py
================================================
================================================
FILE: backend/src/data/github/graphql/user/contribs/__init__.py
================================================
================================================
FILE: backend/src/data/github/graphql/user/contribs/contribs.py
================================================
# import json
from datetime import datetime
from typing import Optional
from src.data.github.graphql.template import get_template
from src.data.github.graphql.user.contribs.models import RawCalendar, RawEvents
def get_user_contribution_calendar(
user_id: str,
start_date: datetime,
end_date: datetime,
access_token: Optional[str] = None,
) -> RawCalendar:
"""Gets contribution calendar for a given time period (max one year)"""
if (end_date - start_date).days > 365:
raise ValueError("date range can be at most 1 year")
query = {
"variables": {
"login": user_id,
"startDate": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"endDate": end_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
},
"query": """
query getUser($login: String!, $startDate: DateTime!, $endDate: DateTime!){
user(login: $login){
contributionsCollection(from: $startDate, to: $endDate){
contributionCalendar{
weeks{
contributionDays{
date
weekday
contributionCount
}
}
}
}
}
}
""",
}
raw_data = get_template(query, access_token)
output = raw_data["data"]["user"]["contributionsCollection"]["contributionCalendar"]
return RawCalendar.model_validate(output)
def get_user_contribution_events(
user_id: str,
start_date: datetime,
end_date: datetime,
max_repos: int = 100,
first: int = 100,
after: str = "",
access_token: Optional[str] = None,
) -> RawEvents:
"""Fetches user contributions (commits, issues, prs, reviews)"""
query = {
"variables": {
"login": user_id,
"startDate": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"endDate": end_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"maxRepos": max_repos,
"first": first,
"after": after,
},
"query": """
query getUser($login: String!, $startDate: DateTime!, $endDate: DateTime!, $maxRepos: Int!, $first: Int!, $after: String!) {
user(login: $login){
contributionsCollection(from: $startDate, to: $endDate){
commitContributionsByRepository(maxRepositories: $maxRepos){
repository{
nameWithOwner,
},
totalCount:contributions(first: 1){
totalCount
}
contributions(first: $first, after: $after){
nodes{
commitCount,
occurredAt,
}
pageInfo{
hasNextPage,
endCursor
}
}
}
issueContributionsByRepository(maxRepositories: $maxRepos){
repository{
nameWithOwner
},
totalCount:contributions(first: 1){
totalCount
}
contributions(first: $first, after: $after){
nodes{
occurredAt,
}
pageInfo{
hasNextPage,
endCursor
}
}
}
pullRequestContributionsByRepository(maxRepositories: $maxRepos){
repository{
nameWithOwner
},
totalCount:contributions(first: 1){
totalCount
}
contributions(first: $first, after: $after){
nodes{
occurredAt,
}
pageInfo{
hasNextPage,
endCursor
}
}
}
pullRequestReviewContributionsByRepository(maxRepositories: $maxRepos){
repository{
nameWithOwner
},
totalCount:contributions(first: 1){
totalCount
}
contributions(first: $first, after: $after){
nodes{
occurredAt,
}
pageInfo{
hasNextPage,
endCursor
}
}
},
repositoryContributions(first: $maxRepos){
totalCount
nodes{
repository{
nameWithOwner,
}
occurredAt,
}
},
},
}
}
""",
}
raw_data = get_template(query, access_token)
output = raw_data["data"]["user"]["contributionsCollection"]
return RawEvents.model_validate(output)
================================================
FILE: backend/src/data/github/graphql/user/contribs/models.py
================================================
from datetime import date, datetime
from typing import List, Optional
from pydantic import BaseModel, Field
class RawCalendarDay(BaseModel):
date: date
weekday: int
count: int = Field(alias="contributionCount")
class RawCalendarWeek(BaseModel):
contribution_days: List[RawCalendarDay] = Field(alias="contributionDays")
class RawCalendar(BaseModel):
weeks: List[RawCalendarWeek]
class RawEventsRepoName(BaseModel):
name: str = Field(alias="nameWithOwner")
class RawEventsCount(BaseModel):
count: int = Field(alias="totalCount")
class RawEventsCommit(BaseModel):
count: int = Field(alias="commitCount")
occurred_at: datetime = Field(alias="occurredAt")
class RawEventsEvent(BaseModel):
occurred_at: datetime = Field(alias="occurredAt")
class RawEventsPageInfo(BaseModel):
has_next_page: bool = Field(alias="hasNextPage")
end_cursor: Optional[str] = Field(alias="endCursor")
class Config:
allow_none = True
class RawEventsCommits(BaseModel):
nodes: List[RawEventsCommit]
page_info: RawEventsPageInfo = Field(alias="pageInfo")
class RawEventsContribs(BaseModel):
nodes: List[RawEventsEvent]
page_info: RawEventsPageInfo = Field(alias="pageInfo")
class RawEventsRepoCommits(BaseModel):
repo: RawEventsRepoName = Field(alias="repository")
count: RawEventsCount = Field(alias="totalCount")
contribs: RawEventsCommits = Field(alias="contributions")
class RawEventsRepo(BaseModel):
repo: RawEventsRepoName = Field(alias="repository")
count: RawEventsCount = Field(alias="totalCount")
contribs: RawEventsContribs = Field(alias="contributions")
class RawEventsRepoEvent(BaseModel):
repo: RawEventsRepoName = Field(alias="repository")
occurred_at: datetime = Field(alias="occurredAt")
class RawEventsRepoContribs(BaseModel):
count: int = Field(alias="totalCount")
nodes: List[RawEventsRepoEvent]
class RawEvents(BaseModel):
commit_contribs_by_repo: List[RawEventsRepoCommits] = Field(
alias="commitContributionsByRepository"
)
issue_contribs_by_repo: List[RawEventsRepo] = Field(
alias="issueContributionsByRepository"
)
pr_contribs_by_repo: List[RawEventsRepo] = Field(
alias="pullRequestContributionsByRepository"
)
review_contribs_by_repo: List[RawEventsRepo] = Field(
alias="pullRequestReviewContributionsByRepository"
)
repo_contribs: RawEventsRepoContribs = Field(alias="repositoryContributions")
================================================
FILE: backend/src/data/github/graphql/user/follows/__init__.py
================================================
================================================
FILE: backend/src/data/github/graphql/user/follows/follows.py
================================================
# import json
from typing import Dict, Optional, Union
from src.data.github.graphql.template import get_template
from src.data.github.graphql.user.follows.models import RawFollows
def get_user_followers(
user_id: str, first: int = 100, after: str = "", access_token: Optional[str] = None
) -> RawFollows:
"""gets user's followers and users following'"""
variables: Dict[str, Union[str, int]] = (
{"login": user_id, "first": first, "after": after}
if after != ""
else {"login": user_id, "first": first}
)
query_str: str = (
"""
query getUser($login: String!, $first: Int!, $after: String!) {
user(login: $login){
followers(first: $first, after: $after){
nodes{
name,
login,
url
}
pageInfo{
hasNextPage,
endCursor
}
}
}
}
"""
if after != ""
else """
query getUser($login: String!, $first: Int!) {
user(login: $login){
followers(first: $first){
nodes{
name,
login,
url
}
pageInfo{
hasNextPage,
endCursor
}
}
}
}
"""
)
query = {
"variables": variables,
"query": query_str,
}
output_dict = get_template(query, access_token)["data"]["user"]["followers"]
return RawFollows.model_validate(output_dict)
def get_user_following(
user_id: str, first: int = 10, after: str = "", access_token: Optional[str] = None
) -> RawFollows:
"""gets user's followers and users following'"""
variables: Dict[str, Union[str, int]] = (
{"login": user_id, "first": first, "after": after}
if after != ""
else {"login": user_id, "first": first}
)
query_str: str = (
"""
query getUser($login: String!, $first: Int!, $after: String!) {
user(login: $login){
following(first: $first, after: $after){
nodes{
name,
login,
url
}
pageInfo{
hasNextPage,
endCursor
}
}
}
}
"""
if after != ""
else """
query getUser($login: String!, $first: Int!) {
user(login: $login){
following(first: $first){
nodes{
name,
login,
url
}
pageInfo{
hasNextPage,
endCursor
}
}
}
}
"""
)
query = {
"variables": variables,
"query": query_str,
}
output_dict = get_template(query, access_token)["data"]["user"]["following"]
return RawFollows.model_validate(output_dict)
================================================
FILE: backend/src/data/github/graphql/user/follows/models.py
================================================
from typing import List, Optional
from pydantic import BaseModel, Field
from src.models import User
class PageInfo(BaseModel):
has_next_page: bool = Field(alias="hasNextPage")
end_cursor: Optional[str] = Field(alias="endCursor")
class Config:
allow_none = True
class RawFollows(BaseModel):
nodes: List[User]
page_info: PageInfo = Field(alias="pageInfo")
================================================
FILE: backend/src/data/github/language_map.py
================================================
import json
import urllib.request
from typing import Any, Dict
BLACKLIST = [".md"]
with urllib.request.urlopen(
"https://raw.githubusercontent.com/blakeembrey/language-map/main/languages.json"
) as url:
data: Dict[str, Dict[str, Any]] = json.loads(url.read().decode())
languages = {
k: v
for k, v in data.items()
if v["type"] in ["programming", "markup"] and "color" in v and "extensions" in v
}
extensions: Dict[str, Dict[str, str]] = {}
for lang_name, lang in languages.items():
for extension in lang["extensions"]:
if extension not in BLACKLIST:
extensions[extension] = {"color": lang["color"], "name": lang_name}
extensions = dict(sorted(extensions.items(), key=lambda x: x[0]))
extensions[".tsx"]["name"] = "TypeScript"
extensions[".tsx"]["color"] = "#2B7489"
extensions[".cs"]["name"] = "C#"
extensions[".cs"]["color"] = "#178600"
extensions[".ml"]["name"] = "OCaml"
extensions[".ml"]["color"] = "#3BE133"
with open("src/data/github/extensions.json", "w") as f:
json.dump(extensions, f, indent=4)
================================================
FILE: backend/src/data/github/rest/__init__.py
================================================
from src.data.github.rest.commit import get_commit_files
from src.data.github.rest.models import RawCommit, RawCommitFile
from src.data.github.rest.repo import get_repo_commits, get_repo_stargazers
from src.data.github.rest.template import RESTError, RESTErrorNotFound
from src.data.github.rest.user import get_user, get_user_starred_repos
__all__ = [
"get_commit_files",
"RawCommit",
"RawCommitFile",
"get_repo_commits",
"get_repo_stargazers",
"RESTError",
"RESTErrorNotFound",
"get_user",
"get_user_starred_repos",
]
================================================
FILE: backend/src/data/github/rest/commit.py
================================================
from typing import List, Optional
from src.data.github.rest.models import RawCommitFile
from src.data.github.rest.template import get_template
BASE_URL = "https://api.github.com/repos/"
def get_commit_files(
owner: str, repo: str, sha: str, access_token: Optional[str] = None
) -> Optional[List[RawCommitFile]]:
"""
Returns raw repository data
:param owner: repository owner
:param repo: repository name
:param sha: commit sha
:param access_token: GitHub access token
:return: repository data
"""
try:
output = get_template(
BASE_URL + owner + "/" + repo + "/commits/" + sha, access_token
)
files = output["files"]
return [RawCommitFile.model_validate(f) for f in files]
except Exception:
return None
================================================
FILE: backend/src/data/github/rest/models.py
================================================
from datetime import datetime
from pydantic import BaseModel
class RawCommit(BaseModel):
timestamp: datetime
node_id: str
class RawCommitFile(BaseModel):
filename: str
additions: int
deletions: int
================================================
FILE: backend/src/data/github/rest/repo.py
================================================
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional
from src.data.github.rest.models import RawCommit
from src.data.github.rest.template import RESTError, get_template, get_template_plural
BASE_URL = "https://api.github.com/repos/"
# NOTE: unused, untested
def get_repo(access_token: str, owner: str, repo: str) -> Dict[str, Any]:
"""
Returns raw repository data
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:return: repository data
"""
return get_template(BASE_URL + owner + "/" + repo, access_token)
# NOTE: unused, untested
def get_repo_languages(
access_token: str, owner: str, repo: str
) -> List[Dict[str, Any]]:
"""
Returns repository language breakdown
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:return: repository language breakdown
"""
return get_template_plural(
BASE_URL + owner + "/" + repo + "/languages", access_token
)
def get_repo_stargazers(
access_token: str, owner: str, repo: str, per_page: int = 100, page: int = 1
) -> List[Dict[str, Any]]:
"""
Returns stargazers with timestamp for repository
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:param per_page: number of items per page
:param page: page number
:return: stargazers with timestamp for repository
"""
return get_template_plural(
BASE_URL + owner + "/" + repo + "/stargazers",
access_token,
per_page=per_page,
page=page,
accept_header="applicaiton/vnd.github.v3.star+json",
)
# NOTE: unused, untested
# does not accept per page, exceeds if necessary
def get_repo_code_frequency(access_token: str, owner: str, repo: str) -> Dict[str, Any]:
"""
Returns code frequency for repository
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:return: code frequency for repository
"""
return get_template(
BASE_URL + owner + "/" + repo + "/stats/code_frequency", access_token
)
# NOTE: unused, untested
def get_repo_commit_activity(
access_token: str, owner: str, repo: str
) -> Dict[str, Any]:
"""
Returns commit activity for past year, broken by week
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:return: commit activity for past year, broken by week
"""
return get_template(
BASE_URL + owner + "/" + repo + "/stats/commit_activity", access_token
)
# NOTE: unused, untested
def get_repo_contributors(access_token: str, owner: str, repo: str) -> Dict[str, Any]:
"""
Returns contributors for a repository
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:return: contributors for a repository
"""
return get_template(
BASE_URL + owner + "/" + repo + "/stats/contributors", access_token
)
# NOTE: unused, untested
def get_repo_weekly_commits(access_token: str, owner: str, repo: str) -> Dict[str, Any]:
"""
Returns contributions by week, owner/non-owner
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:return: contributions by week, owner/non-owner
"""
return get_template(
BASE_URL + owner + "/" + repo + "/stats/participation", access_token
)
# NOTE: unused, untested
def get_repo_hourly_commits(access_token: str, owner: str, repo: str) -> Dict[str, Any]:
"""
Returns contributions by day, hour for repository
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
"""
return get_template(
BASE_URL + owner + "/" + repo + "/stats/punch_card", access_token
)
def get_repo_commits(
owner: str,
repo: str,
user: Optional[str] = None,
since: Optional[datetime] = None,
until: Optional[datetime] = None,
page: int = 1,
access_token: Optional[str] = None,
) -> List[RawCommit]:
"""
Returns most recent commits
:param access_token: GitHub access token
:param owner: repository owner
:param repo: repository name
:param user: optional GitHub user if not owner
:param since: optional datetime to start from
:param until: optional datetime to end at
:param page: optional page number
:return: Up to 100 commits from page
"""
user = user if user is not None else owner
query = BASE_URL + owner + "/" + repo + "/commits?author=" + user
if since is not None:
query += f"&since={str(since)}"
if until is not None:
query += f"&until={str(until)}"
try:
data = get_template_plural(query, access_token, page=page)
def extract_info(x: Any) -> RawCommit:
dt = x["commit"]["committer"]["date"]
temp = {
"timestamp": datetime.strptime(dt, "%Y-%m-%dT%H:%M:%SZ"),
"node_id": x["node_id"],
}
return RawCommit.model_validate(temp)
return list(map(extract_info, data))
except RESTError:
return []
except Exception as e:
logging.exception(e)
return []
================================================
FILE: backend/src/data/github/rest/template.py
================================================
from datetime import datetime
from typing import Any, Dict, List, Optional
import requests
from requests.exceptions import ReadTimeout
from src.constants import TIMEOUT
from src.data.github.utils import get_access_token
s = requests.session()
class RESTError(Exception):
pass
class RESTErrorUnauthorized(RESTError):
pass
class RESTErrorNotFound(RESTError):
pass
class RESTErrorEmptyRepo(RESTError):
pass
class RESTErrorTimeout(RESTError):
pass
def _get_template(
query: str,
params: Dict[str, Any],
accept_header: str,
access_token: Optional[str] = None,
retries: int = 0,
) -> Any:
"""
Internal template for interacting with the GitHub REST API
:param query: The query to be sent to the GitHub API
:param params: The parameters to be sent to the GitHub API
:param access_token: The access token to be sent to the GitHub API
:param accept_header: The accept header to be sent to the GitHub API
:return: The response from the GitHub API
"""
start = datetime.now()
new_access_token = get_access_token(access_token)
headers: Dict[str, str] = {
"Accept": accept_header,
"Authorization": f"bearer {new_access_token}",
}
try:
r = s.get(query, params=params, headers=headers, timeout=TIMEOUT)
except ReadTimeout:
raise RESTErrorTimeout("REST Error: Request Timeout")
if r.status_code == 200:
print("REST API", new_access_token, datetime.now() - start)
return r.json()
if r.status_code == 401:
raise RESTErrorUnauthorized("REST Error: Unauthorized")
if r.status_code == 404:
raise RESTErrorNotFound("REST Error: Not Found")
if r.status_code == 409:
raise RESTErrorEmptyRepo("REST Error: Empty Repository")
if retries < 3:
print("REST Error, Retrying:", new_access_token)
return _get_template(query, params, accept_header, access_token, retries + 1)
raise RESTError(f"REST Error: {str(r.status_code)}")
def get_template(
query: str,
access_token: Optional[str] = None,
accept_header: str = "application/vnd.github.v3+json",
) -> Dict[str, Any]:
"""
Template for interacting with the GitHub REST API (singular)
:param query: The query to be sent to the GitHub API
:param access_token: The access token to be sent to the GitHub API
:param accept_header: The accept header to be sent to the GitHub API
:return: The response from the GitHub API
"""
return _get_template(query, {}, accept_header, access_token)
def get_template_plural(
query: str,
access_token: Optional[str] = None,
per_page: int = 100,
page: int = 1,
accept_header: str = "application/vnd.github.v3+json",
) -> List[Dict[str, Any]]:
"""
Template for interacting with the GitHub REST API (plural)
:param query: The query to be sent to the GitHub API
:param access_token: The access token to be sent to the GitHub API
:param per_page: The number of items to be returned per page
:param page: The page number to be returned
:param accept_header: The accept header to be sent to the GitHub API
:return: The response from the GitHub API
"""
params: Dict[str, str] = {"per_page": str(per_page), "page": str(page)}
return _get_template(query, params, accept_header, access_token)
================================================
FILE: backend/src/data/github/rest/user.py
================================================
from typing import Any, Dict, List
from src.data.github.rest.template import get_template, get_template_plural
BASE_URL = "https://api.github.com/users/"
def get_user(user_id: str, access_token: str) -> Dict[str, Any]:
"""
Returns raw user data
:param user_id: GitHub user id
:param access_token: GitHub access token
"""
return get_template(BASE_URL + user_id, access_token)
def get_user_starred_repos(
user_id: str, access_token: str, per_page: int = 100, page: int = 1
) -> List[Dict[str, Any]]:
"""
Returns list of starred repos
:param user_id: GitHub user id
:param access_token: GitHub access token
:param per_page: number of repos to return per page
"""
return get_template_plural(
BASE_URL + user_id + "/starred",
access_token,
per_page=per_page,
page=page,
accept_header="application/vnd.github.v3.star+json",
)
================================================
FILE: backend/src/data/github/utils.py
================================================
from typing import Optional
from src.data.mongo.secret import get_random_key
def get_access_token(access_token: Optional[str] = None) -> str:
return access_token if access_token is not None else get_random_key()
================================================
FILE: backend/src/data/mongo/__init__.py
================================================
================================================
FILE: backend/src/data/mongo/main.py
================================================
from motor.core import AgnosticCollection
from motor.motor_asyncio import AsyncIOMotorClient
from src.constants import LOCAL, MONGODB_PASSWORD, PROD
def get_conn_str(password: str, database: str) -> str:
return f"mongodb+srv://root:{password}@backend2.e50j8dp.mongodb.net/{database}?retryWrites=true&w=majority"
if LOCAL:
DB = None
elif PROD:
conn_str = get_conn_str(MONGODB_PASSWORD, "prod_backend")
CLIENT = AsyncIOMotorClient(
conn_str, serverSelectionTimeoutMS=5000, tlsInsecure=True
)
DB = CLIENT.prod_backend # type: ignore
else:
conn_str = get_conn_str(MONGODB_PASSWORD, "dev_backend")
CLIENT = AsyncIOMotorClient( # type: ignore
conn_str, serverSelectionTimeoutMS=5000, tlsInsecure=True
)
DB = CLIENT.dev_backend # type: ignore
# Overwrite type since only None if Local=True
SECRETS: AgnosticCollection = None if DB is None else DB.secrets # type: ignore
USERS: AgnosticCollection = None if DB is None else DB.users # type: ignore
USER_MONTHS: AgnosticCollection = None if DB is None else DB.user_months # type: ignore
================================================
FILE: backend/src/data/mongo/secret/__init__.py
================================================
from src.data.mongo.secret.functions import get_random_key, update_keys
__all__ = ["get_random_key", "update_keys"]
================================================
FILE: backend/src/data/mongo/secret/functions.py
================================================
from datetime import timedelta
from random import randint
from typing import Any, Dict, List, Optional, Tuple
from src.constants import TEST_TOKEN
from src.data.mongo.main import SECRETS
from src.data.mongo.secret.models import SecretModel
from src.utils import alru_cache
@alru_cache(ttl=timedelta(minutes=15))
async def get_keys(no_cache: bool = False) -> Tuple[bool, List[str]]:
secrets: Optional[Dict[str, Any]] = await SECRETS.find_one({"project": "main"})
if secrets is None:
return (False, [])
tokens = SecretModel.model_validate(secrets).access_tokens
return (True, tokens)
secret_keys: List[str] = []
async def update_keys(no_cache: bool = False) -> None:
global secret_keys
secret_keys = await get_keys(no_cache=no_cache)
def get_random_key() -> str:
global secret_keys
if len(secret_keys) == 0:
return TEST_TOKEN
return secret_keys[randint(0, len(secret_keys) - 1)]
================================================
FILE: backend/src/data/mongo/secret/models.py
================================================
from typing import List
from pydantic import BaseModel
class SecretModel(BaseModel):
project: str
access_tokens: List[str]
================================================
FILE: backend/src/data/mongo/user/__init__.py
================================================
from src.data.mongo.user.functions import delete_user, is_user_key, update_user
from src.data.mongo.user.get import get_full_user, get_public_user
from src.data.mongo.user.models import FullUserModel, PublicUserModel
__all__ = [
"delete_user",
"is_user_key",
"update_user",
"get_full_user",
"get_public_user",
"FullUserModel",
"PublicUserModel",
]
================================================
FILE: backend/src/data/mongo/user/functions.py
================================================
from typing import Any, Dict, Optional
from src.data.mongo.main import USERS
async def is_user_key(user_id: str, user_key: str) -> bool:
user: Optional[dict[str, str]] = await USERS.find_one(
{"user_id": user_id}, {"user_key": 1}
)
return user is not None and user.get("user_key", "") == user_key
async def update_user(user_id: str, raw_user: Dict[str, Any]) -> None:
await USERS.update_one(
{"user_id": user_id},
{"$set": raw_user},
upsert=True,
)
async def delete_user(user_id: str, user_key: str, use_user_key: bool = True) -> bool:
if use_user_key:
is_key = await is_user_key(user_id, user_key)
if not is_key:
return False
await USERS.delete_one({"user_id": user_id})
return True
================================================
FILE: backend/src/data/mongo/user/get.py
================================================
from typing import Any, Dict, Optional, Tuple
from pydantic import ValidationError
from src.data.mongo.main import USERS
from src.data.mongo.user.models import FullUserModel, PublicUserModel
from src.utils import alru_cache
@alru_cache()
async def get_public_user(
user_id: str, no_cache: bool = False
) -> Tuple[bool, Optional[PublicUserModel]]:
user: Optional[Dict[str, Any]] = await USERS.find_one({"user_id": user_id})
if user is None:
# flag is false, don't cache
return (False, None)
try:
return (True, PublicUserModel.model_validate(user))
except (TypeError, KeyError, ValidationError):
return (False, None)
@alru_cache()
async def get_full_user(
user_id: str, no_cache: bool = False
) -> Tuple[bool, Optional[FullUserModel]]:
user: Optional[Dict[str, Any]] = await USERS.find_one({"user_id": user_id})
if user is None:
# flag is false, don't cache
return (False, None)
try:
return (True, FullUserModel.model_validate(user))
except (TypeError, KeyError, ValidationError):
return (False, None)
================================================
FILE: backend/src/data/mongo/user/models.py
================================================
from typing import Optional
from pydantic import BaseModel, validator
class PublicUserModel(BaseModel):
user_id: str
access_token: str
private_access: Optional[bool]
class Config:
from_attributes = True
validate_assignment = True
@validator("private_access", pre=True, always=True)
def set_name(cls, private_access: Optional[bool]):
return False if private_access is None else private_access
class FullUserModel(PublicUserModel):
user_key: Optional[str]
================================================
FILE: backend/src/data/mongo/user_months/__init__.py
================================================
from src.data.mongo.user_months.functions import set_user_month
from src.data.mongo.user_months.get import get_user_months
from src.data.mongo.user_months.models import UserMonth
__all__ = ["set_user_month", "get_user_months", "UserMonth"]
================================================
FILE: backend/src/data/mongo/user_months/functions.py
================================================
from src.data.mongo.main import USER_MONTHS
from src.data.mongo.user_months.models import UserMonth
async def set_user_month(user_month: UserMonth):
compressed_user_month = user_month.model_dump()
compressed_user_month["data"] = user_month.data.compress()
await USER_MONTHS.update_one(
{"user_id": user_month.user_id, "month": user_month.month},
{"$set": compressed_user_month},
upsert=True,
)
================================================
FILE: backend/src/data/mongo/user_months/get.py
================================================
from datetime import date, datetime
from typing import Any, Dict, List
from src.constants import API_VERSION, USER_WHITELIST
from src.data.mongo.main import USER_MONTHS
from src.data.mongo.user_months.models import UserMonth
from src.models import UserPackage
async def get_user_months(
user_id: str, private_access: bool, start_month: date, end_month: date
) -> List[UserMonth]:
start = datetime(start_month.year, start_month.month, 1)
end = datetime(end_month.year, end_month.month, 28)
today = datetime.now()
filters = {
"user_id": user_id,
"month": {"$gte": start, "$lte": end},
"version": API_VERSION,
}
if private_access:
filters["private"] = True
months: List[Dict[str, Any]] = await USER_MONTHS.find(filters).to_list(length=None) # type: ignore
months_data: List[UserMonth] = []
for month in months:
date_obj: datetime = month["month"]
complete = (
not (date_obj.year == today.year and date_obj.month == today.month)
or user_id in USER_WHITELIST
)
try:
data = UserPackage.decompress(month["data"])
months_data.append(
UserMonth.model_validate(
{
"user_id": user_id,
"month": month["month"],
"version": API_VERSION,
"private": month["private"],
"complete": complete,
"data": data.model_dump(),
}
)
)
except Exception:
pass
return months_data
================================================
FILE: backend/src/data/mongo/user_months/models.py
================================================
from datetime import datetime
from pydantic import BaseModel
from src.models import UserPackage
class UserMonth(BaseModel):
user_id: str
month: datetime
version: float
private: bool
complete: bool
data: UserPackage
================================================
FILE: backend/src/main.py
================================================
from typing import Dict
import sentry_sdk
from dotenv import find_dotenv, load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
load_dotenv(find_dotenv())
# flake8: noqa E402
# add endpoints here (after load dotenv)
from src.constants import PROD, SENTRY_DSN
from src.routers import (
asset_router,
auth_router,
dev_router,
user_router,
wrapped_router,
)
"""
SETUP
"""
app = FastAPI()
origins = [
"http://localhost:3000",
"http://localhost:3001",
"https://githubtrends.io",
"https://www.githubtrends.io",
"https://githubwrapped.io",
"https://www.githubwrapped.io",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
sentry_sdk.init(
SENTRY_DSN,
traces_sample_rate=(1.0 if PROD else 1.0),
)
app.add_middleware(
SentryAsgiMiddleware,
)
@app.get("/")
async def read_root() -> Dict[str, str]:
return {"Hello": "World"}
@app.get("/info")
def get_info() -> Dict[str, bool]:
return {"PROD": PROD}
app.include_router(asset_router, prefix="/assets", tags=["Assets"])
app.include_router(auth_router, prefix="/auth", tags=["Auth"])
app.include_router(dev_router, prefix="/dev", tags=["Dev"])
app.include_router(user_router, prefix="/user", tags=["Users"])
app.include_router(wrapped_router, prefix="/wrapped", tags=["Wrapped"])
================================================
FILE: backend/src/models/__init__.py
================================================
from src.models.user.contribs import (
ContributionDay,
Language,
RepoContributionStats,
UserContributions,
)
from src.models.user.follows import User, UserFollows
from src.models.user.main import UserPackage
from src.models.wrapped.calendar import (
CalendarData,
CalendarDayDatum,
CalendarLanguageDayDatum,
)
from src.models.wrapped.langs import LangData, LangDatum
from src.models.wrapped.main import WrappedPackage
from src.models.wrapped.numeric import ContribStats, LOCStats, MiscStats, NumericData
from src.models.wrapped.repos import RepoData, RepoDatum
from src.models.wrapped.time import DayData, MonthData, TimeDatum
from src.models.wrapped.timestamps import TimestampData, TimestampDatum
__all__ = [
"ContributionDay",
"Language",
"RepoContributionStats",
"UserContributions",
"User",
"UserFollows",
"UserPackage",
"CalendarData",
"CalendarDayDatum",
"CalendarLanguageDayDatum",
"LangData",
"LangDatum",
"WrappedPackage",
"ContribStats",
"LOCStats",
"MiscStats",
"NumericData",
"RepoData",
"RepoDatum",
"DayData",
"MonthData",
"TimeDatum",
"TimestampData",
"TimestampDatum",
]
================================================
FILE: backend/src/models/background.py
================================================
from datetime import date
from typing import Optional
from pydantic import BaseModel
class UpdateUserBackgroundTask(BaseModel):
user_id: str
access_token: Optional[str]
private_access: bool
start_date: Optional[date]
end_date: Optional[date]
================================================
FILE: backend/src/models/svg.py
================================================
from typing import List, Optional
from pydantic import BaseModel
class LanguageStats(BaseModel):
lang: str
loc: int
percent: float
color: Optional[str]
class RepoLanguage(BaseModel):
lang: str
color: Optional[str]
loc: int
class RepoStats(BaseModel):
repo: str
private: bool
langs: List[RepoLanguage]
loc: int
================================================
FILE: backend/src/models/user/__init__.py
================================================
================================================
FILE: backend/src/models/user/contribs.py
================================================
from datetime import date, datetime
from typing import Any, Dict, List, Optional, Tuple
from pydantic import BaseModel
class Language(BaseModel):
color: Optional[str]
additions: int
deletions: int
def compress(self) -> List[Any]:
return [self.color, self.additions, self.deletions]
@classmethod
def decompress(cls, data: List[Any]) -> "Language":
return Language(color=data[0], additions=data[1], deletions=data[2])
def __add__(self, other: "Language") -> "Language":
return Language(
color=self.color,
additions=self.additions + other.additions,
deletions=self.deletions + other.deletions,
)
class ContributionStats(BaseModel):
contribs_count: int
commits_count: int
issues_count: int
prs_count: int
reviews_count: int
repos_count: int
other_count: int
languages: Dict[str, Language]
def compress(self) -> List[Any]:
out: List[Any] = [
[
self.contribs_count,
self.commits_count,
self.issues_count,
self.prs_count,
self.reviews_count,
self.repos_count,
self.other_count,
],
*[[name] + stats.compress() for name, stats in self.languages.items()],
]
return out
@classmethod
def decompress(cls, data: List[Any]) -> "ContributionStats":
return ContributionStats(
contribs_count=data[0][0],
commits_count=data[0][1],
issues_count=data[0][2],
prs_count=data[0][3],
reviews_count=data[0][4],
repos_count=data[0][5],
other_count=data[0][6],
languages={x[0]: Language.decompress(x[1:]) for x in data[1:]},
)
def __add__(self, other: "ContributionStats") -> "ContributionStats":
languages = self.languages
for lang, lang_obj in other.languages.items():
if lang in languages:
languages[lang] += lang_obj
else:
languages[lang] = lang_obj
return ContributionStats(
contribs_count=self.contribs_count + other.contribs_count,
commits_count=self.commits_count + other.commits_count,
issues_count=self.issues_count + other.issues_count,
prs_count=self.prs_count + other.prs_count,
reviews_count=self.reviews_count + other.reviews_count,
repos_count=self.repos_count + other.repos_count,
other_count=self.other_count + other.other_count,
languages=languages,
)
@classmethod
def empty(cls) -> "ContributionStats":
return ContributionStats(
contribs_count=0,
commits_count=0,
issues_count=0,
prs_count=0,
reviews_count=0,
repos_count=0,
other_count=0,
languages={},
)
class ContributionLists(BaseModel):
commits: List[datetime]
issues: List[datetime]
prs: List[datetime]
reviews: List[datetime]
repos: List[datetime]
def compress(self) -> List[Any]:
return [self.commits, self.issues, self.prs, self.reviews, self.repos]
@classmethod
def decompress(cls, data: List[Any]) -> "ContributionLists":
return ContributionLists(
commits=data[0], issues=data[1], prs=data[2], reviews=data[3], repos=data[4]
)
class ContributionDay(BaseModel):
date: str
weekday: int
stats: ContributionStats
lists: ContributionLists
def compress(self) -> List[Any]:
return [
self.date,
self.weekday,
self.stats.compress(),
self.lists.compress(),
]
@classmethod
def decompress(cls, data: List[Any]) -> "ContributionDay":
return ContributionDay(
date=data[0],
weekday=data[1],
stats=ContributionStats.decompress(data[2]),
lists=ContributionLists.decompress(data[3]),
)
class RepoContributionStats(ContributionStats, BaseModel):
private: bool
contribs_count: int
commits_count: int
issues_count: int
prs_count: int
reviews_count: int
repos_count: int
other_count: int
languages: Dict[str, Language]
def compress(self) -> List[Any]:
out = super().compress()
out[0].append(self.private)
return out
@classmethod
def decompress(cls, data: List[Any]) -> "RepoContributionStats":
contribs = super().decompress(data).model_dump()
contribs["private"] = data[0][7]
return RepoContributionStats(**contribs)
def __add__( # type: ignore
self, other: "RepoContributionStats"
) -> "RepoContributionStats":
new_self = ContributionStats(**self.model_dump())
new_other = ContributionStats(**other.model_dump())
combined = (new_self + new_other).model_dump()
combined["private"] = self.private
return RepoContributionStats(**combined)
class UserContributions(BaseModel):
total_stats: ContributionStats
public_stats: ContributionStats
total: List[ContributionDay]
public: List[ContributionDay]
repo_stats: Dict[str, RepoContributionStats]
repos: Dict[str, List[ContributionDay]]
def compress(self) -> List[Any]:
new_total_stats = self.total_stats.compress()
new_public_stats = self.public_stats.compress()
new_total = [x.compress() for x in self.total]
new_public = [x.compress() for x in self.public]
new_repo_stats = {k: v.compress() for k, v in self.repo_stats.items()}
new_repos = {k: [x.compress() for x in v] for k, v in self.repos.items()}
return [
new_total_stats,
new_public_stats,
new_total,
new_public,
new_repo_stats,
new_repos,
]
@classmethod
def decompress(cls, data: List[Any]) -> "UserContributions":
total_stats = ContributionStats.decompress(data[0])
public_stats = ContributionStats.decompress(data[1])
total = [ContributionDay.decompress(x) for x in data[2]]
public = [ContributionDay.decompress(x) for x in data[3]]
repo_stats = {
k: RepoContributionStats.decompress(v) for k, v in data[4].items()
}
repos = {
k: [ContributionDay.decompress(x) for x in v] for k, v in data[5].items()
}
return UserContributions(
total_stats=total_stats,
public_stats=public_stats,
total=total,
public=public,
repo_stats=repo_stats,
repos=repos,
)
def __add__(self, other: "UserContributions") -> "UserContributions":
new_total_stats = self.total_stats + other.total_stats
new_public_stats = self.public_stats + other.public_stats
new_total = sorted(self.total + other.total, key=lambda x: x.date)
new_public = sorted(self.public + other.public, key=lambda x: x.date)
new_repo_stats = self.repo_stats
for repo, stats in other.repo_stats.items():
if repo in new_repo_stats:
new_repo_stats[repo] += stats
else:
new_repo_stats[repo] = stats
new_repos = self.repos
for repo, days in other.repos.items():
if repo in new_repos:
new_repos[repo] = sorted(new_repos[repo] + days, key=lambda x: x.date)
else:
new_repos[repo] = days
return UserContributions(
total_stats=new_total_stats,
public_stats=new_public_stats,
total=new_total,
public=new_public,
repo_stats=new_repo_stats,
repos=new_repos,
)
@staticmethod
def trim_contribs(
contribs: List[ContributionDay], start_date: date, end_date: date
) -> Tuple[List[ContributionDay], ContributionStats]:
new_total: List[ContributionDay] = []
for day in contribs:
curr_date = datetime.strptime(day.date, "%Y-%m-%d").date()
if curr_date >= start_date and curr_date <= end_date:
new_total.append(day)
new_languages: Dict[str, Language] = {}
for day in new_total:
for lang in day.stats.languages:
if lang in new_languages:
new_languages[lang] += day.stats.languages[lang]
else:
new_languages[lang] = day.stats.languages[lang]
new_stats = ContributionStats(
contribs_count=sum(x.stats.contribs_count for x in new_total),
commits_count=sum(x.stats.commits_count for x in new_total),
issues_count=sum(x.stats.issues_count for x in new_total),
prs_count=sum(x.stats.prs_count for x in new_total),
reviews_count=sum(x.stats.reviews_count for x in new_total),
repos_count=sum(x.stats.repos_count for x in new_total),
other_count=sum(x.stats.other_count for x in new_total),
languages=new_languages,
)
return new_total, new_stats
def trim(self, start: date, end: date) -> "UserContributions":
new_total, new_total_stats = self.trim_contribs(self.total, start, end)
new_public, new_public_stats = self.trim_contribs(self.public, start, end)
new_repos_dict: Dict[str, List[ContributionDay]] = {}
new_repo_stats_dict: Dict[str, RepoContributionStats] = {}
for repo_name, repo in self.repos.items():
new_repo_total, _new_repo_stats = self.trim_contribs(repo, start, end)
if len(new_repo_total) > 0:
new_repos_dict[repo_name] = new_repo_total
raw_new_repo_stats = _new_repo_stats.model_dump()
raw_new_repo_stats["private"] = self.repo_stats[repo_name].private
new_repo_stats = RepoContributionStats(**raw_new_repo_stats)
new_repo_stats_dict[repo_name] = new_repo_stats
return UserContributions(
total_stats=new_total_stats,
public_stats=new_public_stats,
total=new_total,
public=new_public,
repo_stats=new_repo_stats_dict,
repos=new_repos_dict,
)
@classmethod
def empty(cls) -> "UserContributions":
return UserContributions(
total_stats=ContributionStats.empty(),
public_stats=ContributionStats.empty(),
total=[],
public=[],
repo_stats={},
repos={},
)
================================================
FILE: backend/src/models/user/follows.py
================================================
from typing import List, Optional
from pydantic import BaseModel
class User(BaseModel):
name: Optional[str]
login: str
url: str
class Config:
allow_none = True
class UserFollows(BaseModel):
followers: List[User]
following: List[User]
================================================
FILE: backend/src/models/user/main.py
================================================
from datetime import date
from typing import Any, Dict
from pydantic import BaseModel
from src.models.user.contribs import UserContributions
# from src.models.user.follows import UserFollows
class UserPackage(BaseModel):
contribs: UserContributions
incomplete: bool = False
def compress(self):
return {
"c": self.contribs.compress(),
}
@classmethod
def decompress(cls, data: Dict[str, Any]) -> "UserPackage":
return UserPackage(
contribs=UserContributions.decompress(data["c"]),
)
def __add__(self, other: "UserPackage") -> "UserPackage":
return UserPackage(contribs=self.contribs + other.contribs)
def trim(self, start: date, end: date) -> "UserPackage":
return UserPackage(contribs=self.contribs.trim(start, end))
@classmethod
def empty(cls) -> "UserPackage":
return UserPackage(contribs=UserContributions.empty())
================================================
FILE: backend/src/models/wrapped/__init__.py
================================================
================================================
FILE: backend/src/models/wrapped/calendar.py
================================================
from typing import Dict, List
from pydantic import BaseModel
class CalendarLanguageDayDatum(BaseModel):
loc_added: int
loc_changed: int
class CalendarDayDatum(BaseModel):
day: str
contribs: int
commits: int
issues: int
prs: int
reviews: int
loc_added: int
loc_changed: int
top_langs: Dict[str, CalendarLanguageDayDatum]
class CalendarData(BaseModel):
days: List[CalendarDayDatum]
@classmethod
def empty(cls) -> "CalendarData":
return CalendarData(days=[])
================================================
FILE: backend/src/models/wrapped/langs.py
================================================
from typing import List
from pydantic import BaseModel
class LangDatum(BaseModel):
id: str
label: str
value: int
formatted_value: str
color: str
class LangData(BaseModel):
langs_changed: List[LangDatum]
langs_added: List[LangDatum]
@classmethod
def empty(cls) -> "LangData":
return LangData(langs_changed=[], langs_added=[])
================================================
FILE: backend/src/models/wrapped/main.py
================================================
from pydantic import BaseModel
from src.models.wrapped.calendar import CalendarData
from src.models.wrapped.langs import LangData
from src.models.wrapped.numeric import NumericData
from src.models.wrapped.repos import RepoData
from src.models.wrapped.time import DayData, MonthData
from src.models.wrapped.timestamps import TimestampData
class WrappedPackage(BaseModel):
month_data: MonthData
day_data: DayData
calendar_data: CalendarData
numeric_data: NumericData
repo_data: RepoData
lang_data: LangData
timestamp_data: TimestampData
incomplete: bool = False
@classmethod
def empty(cls) -> "WrappedPackage":
return WrappedPackage(
month_data=MonthData.empty(),
day_data=DayData.empty(),
calendar_data=CalendarData.empty(),
numeric_data=NumericData.empty(),
repo_data=RepoData.empty(),
lang_data=LangData.empty(),
timestamp_data=TimestampData.empty(),
)
================================================
FILE: backend/src/models/wrapped/numeric.py
================================================
from typing import Optional, Tuple
from pydantic import BaseModel
class ContribStats(BaseModel):
contribs: int
commits: int
issues: int
prs: int
reviews: int
other: int
@classmethod
def empty(cls) -> "ContribStats":
return ContribStats(
contribs=0,
commits=0,
issues=0,
prs=0,
reviews=0,
other=0,
)
class MiscStats(BaseModel):
total_days: int
longest_streak: int
longest_streak_days: Tuple[int, int, str, str]
longest_gap: int
longest_gap_days: Tuple[int, int, str, str]
weekend_percent: int
best_day_count: int
best_day_date: Optional[str]
best_day_index: Optional[int]
@classmethod
def empty(cls) -> "MiscStats":
return MiscStats(
total_days=0,
longest_streak=0,
longest_streak_days=(0, 0, "", ""),
longest_gap=0,
longest_gap_days=(0, 0, "", ""),
weekend_percent=0,
best_day_count=0,
best_day_date=None,
best_day_index=None,
)
class LOCStats(BaseModel):
loc_additions: str
loc_deletions: str
loc_changed: str
loc_added: str
loc_additions_per_commit: int
loc_deletions_per_commit: int
loc_changed_per_day: int
@classmethod
def empty(cls) -> "LOCStats":
return LOCStats(
loc_additions="0",
loc_deletions="0",
loc_changed="0",
loc_added="0",
loc_additions_per_commit=0,
loc_deletions_per_commit=0,
loc_changed_per_day=0,
)
class NumericData(BaseModel):
contribs: ContribStats
misc: MiscStats
loc: LOCStats
@classmethod
def empty(cls) -> "NumericData":
return NumericData(
contribs=ContribStats.empty(),
misc=MiscStats.empty(),
loc=LOCStats.empty(),
)
================================================
FILE: backend/src/models/wrapped/repos.py
================================================
from typing import List
from pydantic import BaseModel
class RepoDatum(BaseModel):
id: int
label: str
value: int
formatted_value: str
class RepoData(BaseModel):
repos_changed: List[RepoDatum]
repos_added: List[RepoDatum]
@classmethod
def empty(cls) -> "RepoData":
return RepoData(repos_changed=[], repos_added=[])
================================================
FILE: backend/src/models/wrapped/time.py
================================================
from typing import List
from pydantic import BaseModel
class TimeDatum(BaseModel):
index: int
contribs: int
loc_changed: int
formatted_loc_changed: str
class MonthData(BaseModel):
months: List[TimeDatum]
@classmethod
def empty(cls) -> "MonthData":
return MonthData(months=[])
class DayData(BaseModel):
days: List[TimeDatum]
@classmethod
def empty(cls) -> "DayData":
return DayData(days=[])
================================================
FILE: backend/src/models/wrapped/timestamps.py
================================================
from typing import List
from pydantic import BaseModel
class TimestampDatum(BaseModel):
type: str
weekday: int
timestamp: int
class TimestampData(BaseModel):
contribs: List[TimestampDatum]
@classmethod
def empty(cls) -> "TimestampData":
return TimestampData(contribs=[])
================================================
FILE: backend/src/processing/auth.py
================================================
from typing import Any, Dict, Optional, Tuple
from src.data.github.auth import authenticate as github_authenticate
from src.data.mongo.user import (
PublicUserModel,
delete_user as db_delete_user,
get_public_user as db_get_public_user,
update_user as db_update_user,
)
from src.models.background import UpdateUserBackgroundTask
# frontend first calls set_user_key with code and user_key
# next they call authenticate which determines the user_id to associate with the code/user_key
# these actions should happen sequentially, so in-memory storage is fine
code_key_map: Dict[str, str] = {}
async def set_user_key(code: str, user_key: str) -> str:
code_key_map[code] = user_key
return user_key
async def authenticate(
code: str, private_access: bool
) -> Tuple[str, Optional[UpdateUserBackgroundTask]]:
user_id, access_token = await github_authenticate(code)
curr_user: Optional[PublicUserModel] = await db_get_public_user(user_id)
raw_user: Dict[str, Any] = {
"user_id": user_id,
"access_token": access_token,
"user_key": code_key_map.get(code, None),
"private_access": private_access,
}
background_task = None
if curr_user is not None:
curr_private_access = curr_user.private_access
new_private_access = curr_private_access or private_access
raw_user["private_access"] = new_private_access
if new_private_access != curr_private_access:
# new private access status
background_task = UpdateUserBackgroundTask(
user_id=user_id,
access_token=access_token,
private_access=new_private_access,
start_date=None,
end_date=None,
)
else:
# first time sign up
background_task = UpdateUserBackgroundTask(
user_id=user_id,
access_token=access_token,
private_access=private_access,
start_date=None,
end_date=None,
)
await db_update_user(user_id, raw_user)
return user_id, background_task
async def delete_user(user_id: str, user_key: str, use_user_key: bool = True) -> bool:
return await db_delete_user(user_id, user_key, use_user_key)
================================================
FILE: backend/src/processing/user/__init__.py
================================================
from src.processing.user.commits import get_top_languages, get_top_repos
from src.processing.user.svg import svg_base
__all__ = ["get_top_languages", "get_top_repos", "svg_base"]
================================================
FILE: backend/src/processing/user/commits.py
================================================
from typing import Any, Dict, List, Optional, Tuple, Union
from src.constants import DEFAULT_COLOR
from src.models import UserPackage
from src.models.svg import LanguageStats, RepoStats
dict_type = Dict[str, Union[str, int, float]]
def loc_metric_func(loc_metric: str, additions: int, deletions: int) -> int:
if loc_metric == "changed":
return additions + deletions
return additions - deletions
def get_top_languages(
data: UserPackage, loc_metric: str, include_private: bool
) -> Tuple[List[LanguageStats], int]:
raw_languages = (
data.contribs.total_stats.languages
if include_private
else data.contribs.public_stats.languages
)
languages_list = [
LanguageStats(
lang=lang,
color=stats.color or DEFAULT_COLOR,
loc=loc_metric_func(loc_metric, stats.additions, stats.deletions),
percent=-1,
)
for lang, stats in raw_languages.items()
]
languages_list = list(filter(lambda x: x.loc > 0, languages_list))
total_loc = sum(x.loc for x in languages_list) + 1
total = LanguageStats(lang="Total", color=None, loc=total_loc, percent=100)
languages_list = sorted(languages_list, key=lambda x: x.loc, reverse=True)
other = LanguageStats(lang="Other", color="#ededed", loc=0, percent=-1)
for language in languages_list[4:]:
other.loc = other.loc + language.loc
languages_list = [total] + languages_list[:4] + [other]
new_languages_list: List[LanguageStats] = []
for lang in languages_list:
lang.percent = float(round(100 * lang.loc / total_loc, 2))
if lang.percent > 1: # 1% minimum to show
new_languages_list.append(LanguageStats.model_validate(lang))
commits_excluded = data.contribs.public_stats.other_count
if include_private:
commits_excluded = data.contribs.total_stats.other_count
return new_languages_list, commits_excluded
def get_top_repos(
data: UserPackage, loc_metric: str, include_private: bool, group: str
) -> Tuple[List[RepoStats], int]:
repos: List[Any] = [
{
"repo": repo,
"private": repo_stats.private,
"langs": [
{
"lang": x[0],
"color": x[1].color,
"loc": loc_metric_func(loc_metric, x[1].additions, x[1].deletions),
}
for x in list(repo_stats.languages.items())
],
}
for repo, repo_stats in data.contribs.repo_stats.items()
if include_private or not repo_stats.private
]
for repo in repos:
repo["loc"] = sum(x["loc"] for x in repo["langs"]) # first estimate
repos = list(filter(lambda x: x["loc"] > 0, repos))
for repo in repos:
repo["langs"] = [x for x in repo["langs"] if x["loc"] > 0.05 * repo["loc"]]
repo["loc"] = sum(x["loc"] for x in repo["langs"]) # final estimate
repos = sorted(repos, key=lambda x: x["loc"], reverse=True)
new_repos = [
RepoStats.model_validate(x) for x in repos if x["loc"] > 0.01 * repos[0]["loc"]
]
commits_excluded = data.contribs.public_stats.other_count
if include_private:
commits_excluded = data.contribs.total_stats.other_count
# With n bars, group from n onwards into the last bar
bars = 4 # TODO: make this configurable (see issues)
if group == "none" or len(new_repos) <= bars:
return new_repos[:bars], commits_excluded
out_repos = []
other_repos = []
if group == "other":
out_repos = new_repos[: bars - 1]
other_repos = new_repos[bars - 1 :]
elif group == "private":
public_repos = [x for x in new_repos if not x.private]
private_repos = [x for x in new_repos if x.private]
if len(public_repos) < 4 and len(private_repos) > 0:
public_repos += private_repos[: bars - len(public_repos) - 1]
private_repos = private_repos[bars - len(public_repos) - 1 :]
out_repos = sorted(public_repos[: bars - 1], key=lambda x: x.loc, reverse=True)
other_repos = public_repos[bars - 1 :] + private_repos
else:
raise ValueError("Invalid group value")
other: Dict[str, Tuple[int, Optional[str]]] = {}
for repo in other_repos:
for _lang in repo.langs:
lang = _lang.lang
if lang not in other:
other[lang] = (0, _lang.color)
other[lang] = (other[lang][0] + _lang.loc, other[lang][1])
out_repos.append(
RepoStats(
repo="other/repos",
private=False,
langs=[{"lang": k, "loc": v[0], "color": v[1]} for k, v in other.items()], # type: ignore
loc=sum(v[0] for v in other.values()),
)
)
return out_repos, commits_excluded
================================================
FILE: backend/src/processing/user/svg.py
================================================
from datetime import date
from typing import Optional, Tuple
from src.aggregation.layer2.user import get_user, get_user_demo
from src.models import UserPackage
from src.models.background import UpdateUserBackgroundTask
from src.utils import use_time_range
async def svg_base(
user_id: str,
start_date: date,
end_date: date,
time_range: str,
demo: bool,
no_cache: bool = False,
) -> Tuple[Optional[UserPackage], bool, Optional[UpdateUserBackgroundTask], str]:
# process time_range, start_date, end_date
time_range = "one_month" if demo else time_range
start_date, end_date, time_str = use_time_range(time_range, start_date, end_date)
complete = True # overwritten later if not complete
background_task = None
# fetch data, either using demo or user method
if demo:
output = await get_user_demo(user_id, start_date, end_date, no_cac
gitextract_vuc7bnqv/
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── backend.yaml
│ └── frontend.yaml
├── .gitignore
├── LICENSE
├── README.md
├── backend/
│ ├── .coveragerc
│ ├── .env-template
│ ├── .flake8
│ ├── .gcloudignore
│ ├── .pre-commit-config.yaml
│ ├── README.md
│ ├── deploy/
│ │ ├── app.yaml
│ │ ├── cloudbuild.yaml
│ │ └── dispatch.yaml
│ ├── package.json
│ ├── pyproject.toml
│ ├── requirements.txt
│ ├── scripts/
│ │ ├── __init__.py
│ │ ├── delete_old_data.py
│ │ └── local.py
│ ├── src/
│ │ ├── __init__.py
│ │ ├── aggregation/
│ │ │ ├── layer0/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── contributions.py
│ │ │ │ ├── follows.py
│ │ │ │ ├── languages.py
│ │ │ │ └── package.py
│ │ │ ├── layer1/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── auth.py
│ │ │ │ └── user.py
│ │ │ └── layer2/
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ └── user.py
│ │ ├── constants.py
│ │ ├── data/
│ │ │ ├── __init__.py
│ │ │ ├── github/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── auth/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── main.py
│ │ │ │ ├── extensions.json
│ │ │ │ ├── graphql/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── commit.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── repo.py
│ │ │ │ │ ├── template.py
│ │ │ │ │ └── user/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── contribs/
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── contribs.py
│ │ │ │ │ │ └── models.py
│ │ │ │ │ └── follows/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── follows.py
│ │ │ │ │ └── models.py
│ │ │ │ ├── language_map.py
│ │ │ │ ├── rest/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── commit.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── repo.py
│ │ │ │ │ ├── template.py
│ │ │ │ │ └── user.py
│ │ │ │ └── utils.py
│ │ │ └── mongo/
│ │ │ ├── __init__.py
│ │ │ ├── main.py
│ │ │ ├── secret/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── functions.py
│ │ │ │ └── models.py
│ │ │ ├── user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── functions.py
│ │ │ │ ├── get.py
│ │ │ │ └── models.py
│ │ │ └── user_months/
│ │ │ ├── __init__.py
│ │ │ ├── functions.py
│ │ │ ├── get.py
│ │ │ └── models.py
│ │ ├── main.py
│ │ ├── models/
│ │ │ ├── __init__.py
│ │ │ ├── background.py
│ │ │ ├── svg.py
│ │ │ ├── user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── contribs.py
│ │ │ │ ├── follows.py
│ │ │ │ └── main.py
│ │ │ └── wrapped/
│ │ │ ├── __init__.py
│ │ │ ├── calendar.py
│ │ │ ├── langs.py
│ │ │ ├── main.py
│ │ │ ├── numeric.py
│ │ │ ├── repos.py
│ │ │ ├── time.py
│ │ │ └── timestamps.py
│ │ ├── processing/
│ │ │ ├── auth.py
│ │ │ ├── user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── commits.py
│ │ │ │ └── svg.py
│ │ │ └── wrapped/
│ │ │ ├── __init__.py
│ │ │ ├── calendar.py
│ │ │ ├── langs.py
│ │ │ ├── main.py
│ │ │ ├── numeric.py
│ │ │ ├── package.py
│ │ │ ├── repos.py
│ │ │ ├── time.py
│ │ │ └── timestamps.py
│ │ ├── render/
│ │ │ ├── __init__.py
│ │ │ ├── error.py
│ │ │ ├── style.py
│ │ │ ├── template.py
│ │ │ ├── top_langs.py
│ │ │ └── top_repos.py
│ │ ├── routers/
│ │ │ ├── __init__.py
│ │ │ ├── assets/
│ │ │ │ ├── __init__.py
│ │ │ │ └── assets.py
│ │ │ ├── auth/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── main.py
│ │ │ │ ├── standalone.py
│ │ │ │ └── website.py
│ │ │ ├── background.py
│ │ │ ├── decorators.py
│ │ │ ├── dev.py
│ │ │ ├── users/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── db.py
│ │ │ │ ├── main.py
│ │ │ │ └── svg.py
│ │ │ └── wrapped.py
│ │ └── utils/
│ │ ├── __init__.py
│ │ ├── alru_cache.py
│ │ ├── decorators.py
│ │ ├── gather.py
│ │ └── utils.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── aggregation/
│ │ │ ├── __init__.py
│ │ │ └── layer0/
│ │ │ ├── __init__.py
│ │ │ ├── test_contributions.py
│ │ │ └── test_follows.py
│ │ ├── data/
│ │ │ ├── __init__.py
│ │ │ └── github/
│ │ │ ├── __init__.py
│ │ │ ├── auth/
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_main.py
│ │ │ ├── graphql/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_commits.py
│ │ │ │ ├── test_repo.py
│ │ │ │ ├── test_user_contribs.py
│ │ │ │ └── test_user_follows.py
│ │ │ └── rest/
│ │ │ ├── __init__.py
│ │ │ ├── test_commit.py
│ │ │ └── test_repo.py
│ │ └── utils/
│ │ ├── __init__.py
│ │ └── test_alru_cache.py
│ └── transfer_mongodb.bash
├── docs/
│ ├── API.md
│ ├── CONTRIBUTING.md
│ ├── FAQ.md
│ └── THEME.md
└── frontend/
├── .env-template
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── .yarnrc
├── README.md
├── deploy/
│ └── Dockerfile
├── package.json
├── public/
│ ├── _redirects
│ ├── manifest.json
│ ├── robots.txt
│ ├── trends.html
│ └── wrapped.html
├── src/
│ ├── api/
│ │ ├── index.js
│ │ ├── user.js
│ │ └── wrapped.js
│ ├── assets/
│ │ └── notes.txt
│ ├── components/
│ │ ├── Card/
│ │ │ ├── Card.js
│ │ │ ├── SVG.js
│ │ │ └── index.js
│ │ ├── Generic/
│ │ │ ├── Button.js
│ │ │ ├── Checkbox.js
│ │ │ ├── Input.js
│ │ │ └── index.js
│ │ ├── Home/
│ │ │ ├── CheckboxSection.js
│ │ │ ├── DateRangeSection.js
│ │ │ ├── Progress.js
│ │ │ ├── Section.js
│ │ │ └── index.js
│ │ ├── Preview/
│ │ │ ├── Preview.js
│ │ │ └── index.js
│ │ ├── Wrapped/
│ │ │ ├── Organization.js
│ │ │ ├── Specifics/
│ │ │ │ ├── Bar.js
│ │ │ │ ├── Calendar.js
│ │ │ │ ├── Numeric.js
│ │ │ │ ├── Pie.js
│ │ │ │ ├── Radar.js
│ │ │ │ ├── Swarm.js
│ │ │ │ └── index.js
│ │ │ ├── Templates/
│ │ │ │ ├── Bar.js
│ │ │ │ ├── Numeric.js
│ │ │ │ ├── Pie.js
│ │ │ │ ├── Swarm.js
│ │ │ │ ├── index.js
│ │ │ │ └── theme.js
│ │ │ └── index.js
│ │ └── index.js
│ ├── constants.js
│ ├── index.css
│ ├── index.js
│ ├── pages/
│ │ ├── App/
│ │ │ ├── AppTrends.js
│ │ │ ├── AppWrapped.js
│ │ │ ├── Footer.js
│ │ │ ├── Header.js
│ │ │ └── index.js
│ │ ├── Auth/
│ │ │ ├── SignUp.js
│ │ │ └── index.js
│ │ ├── Demo/
│ │ │ ├── Demo.js
│ │ │ └── index.js
│ │ ├── Home/
│ │ │ ├── Home.js
│ │ │ ├── index.js
│ │ │ └── stages/
│ │ │ ├── Customize.js
│ │ │ ├── Display.js
│ │ │ ├── SelectCard.js
│ │ │ ├── Theme.js
│ │ │ └── index.js
│ │ ├── Landing/
│ │ │ ├── Landing.js
│ │ │ └── index.js
│ │ ├── Misc/
│ │ │ ├── NoMatch.js
│ │ │ ├── Redirect.js
│ │ │ └── index.js
│ │ ├── Settings/
│ │ │ ├── Settings.js
│ │ │ └── index.js
│ │ └── Wrapped/
│ │ ├── SelectUser.js
│ │ ├── Wrapped.js
│ │ ├── index.js
│ │ └── sections/
│ │ ├── Loading.js
│ │ ├── LoadingV2.js
│ │ ├── index.js
│ │ └── loading.css
│ ├── redux/
│ │ ├── actions/
│ │ │ └── userActions.js
│ │ ├── logger.js
│ │ ├── reducers/
│ │ │ ├── index.js
│ │ │ └── user.js
│ │ └── store.js
│ └── utils.js
└── tailwind.config.js
SYMBOL INDEX (335 symbols across 102 files)
FILE: backend/scripts/delete_old_data.py
function get_filters (line 20) | def get_filters(cutoff_date: datetime) -> Any:
function count_old_rows (line 29) | async def count_old_rows(cutoff_date: datetime) -> int:
function delete_old_rows (line 35) | async def delete_old_rows(cutoff_date: datetime):
function main (line 41) | async def main():
FILE: backend/scripts/local.py
function parse_args (line 19) | def parse_args():
function main (line 48) | async def main():
FILE: backend/src/aggregation/layer0/contributions.py
class ContribsList (line 36) | class ContribsList:
method __init__ (line 37) | def __init__(self):
method add (line 44) | def add(self, label: str, event: Union[RawEventsCommit, RawEventsEvent]):
function get_user_all_contribution_events (line 57) | def get_user_all_contribution_events(
function get_all_commit_info (line 100) | def get_all_commit_info(
function get_all_commit_languages (line 120) | async def get_all_commit_languages(
function get_cleaned_contributions (line 217) | async def get_cleaned_contributions(
class StatsContainer (line 286) | class StatsContainer:
method __init__ (line 287) | def __init__(self):
method add_stat (line 297) | def add_stat(self, label: str, count: int, add: bool = False) -> None:
method to_dict (line 314) | def to_dict(self) -> Dict[str, Any]:
class ListsContainer (line 327) | class ListsContainer:
method __init__ (line 328) | def __init__(self):
method add_list (line 335) | def add_list(self, label: str, times: List[datetime]) -> None:
method to_dict (line 347) | def to_dict(self) -> Dict[str, Any]:
class DateContainer (line 357) | class DateContainer:
method __init__ (line 358) | def __init__(self):
method add_stat (line 364) | def add_stat(
method to_dict (line 370) | def to_dict(self) -> Dict[str, Any]:
function get_contributions (line 380) | async def get_contributions(
FILE: backend/src/aggregation/layer0/follows.py
function get_user_follows (line 10) | def get_user_follows(user_id: str, access_token: Optional[str]) -> UserF...
FILE: backend/src/aggregation/layer0/languages.py
class CommitLanguages (line 11) | class CommitLanguages:
method __init__ (line 12) | def __init__(self):
method __repr__ (line 15) | def __repr__(self):
method add_lines (line 18) | def add_lines(
method normalize (line 32) | def normalize(self, add_ratio: float, del_ratio: float):
method __add__ (line 39) | def __add__(self, other: "CommitLanguages"):
method to_dict (line 47) | def to_dict(self) -> Dict[str, Any]:
function get_commit_languages (line 51) | def get_commit_languages(
FILE: backend/src/aggregation/layer0/package.py
function get_user_data (line 10) | async def get_user_data(
FILE: backend/src/aggregation/layer1/auth.py
function get_valid_github_user (line 17) | async def get_valid_github_user(user_id: str) -> Optional[str]:
function get_valid_db_user (line 29) | async def get_valid_db_user(user_id: str) -> bool:
function get_repo_stargazers (line 35) | async def get_repo_stargazers(
function get_user_stars (line 50) | async def get_user_stars(user_id: str) -> List[str]:
FILE: backend/src/aggregation/layer1/user.py
function query_user_month (line 21) | async def query_user_month(
function query_user (line 68) | async def query_user(
FILE: backend/src/aggregation/layer2/auth.py
function check_github_user_exists (line 15) | async def check_github_user_exists(user_id: str) -> Optional[str]:
function check_db_user_exists (line 19) | async def check_db_user_exists(user_id: str) -> bool:
function check_user_starred_repo (line 23) | async def check_user_starred_repo(
function get_is_valid_user (line 40) | async def get_is_valid_user(user_id: str) -> Tuple[bool, str]:
FILE: backend/src/aggregation/layer2/user.py
function _get_user (line 16) | async def _get_user(
function get_user (line 37) | async def get_user(
function get_user_demo (line 65) | async def get_user_demo(
FILE: backend/src/data/github/auth/main.py
function get_unknown_user (line 11) | def get_unknown_user(access_token: str) -> Optional[str]:
class OAuthError (line 26) | class OAuthError(Exception):
function authenticate (line 30) | async def authenticate(code: str) -> Tuple[str, str]:
FILE: backend/src/data/github/graphql/commit.py
function get_commits (line 14) | def get_commits(
FILE: backend/src/data/github/graphql/models.py
class RawCommitPRFileNode (line 6) | class RawCommitPRFileNode(BaseModel):
class RawCommitPRFile (line 12) | class RawCommitPRFile(BaseModel):
class RawCommitPRNode (line 16) | class RawCommitPRNode(BaseModel):
class RawCommitPR (line 23) | class RawCommitPR(BaseModel):
class RawCommit (line 27) | class RawCommit(BaseModel):
class RawRepoLanguageNode (line 35) | class RawRepoLanguageNode(BaseModel):
class RawRepoLanguageEdge (line 40) | class RawRepoLanguageEdge(BaseModel):
class RawRepoLanguage (line 45) | class RawRepoLanguage(BaseModel):
class RawRepo (line 51) | class RawRepo(BaseModel):
FILE: backend/src/data/github/graphql/repo.py
function get_repo (line 7) | def get_repo(
FILE: backend/src/data/github/graphql/template.py
class GraphQLError (line 14) | class GraphQLError(Exception):
class GraphQLErrorMissingNode (line 18) | class GraphQLErrorMissingNode(Exception):
method __init__ (line 19) | def __init__(self, node: int, *args: Tuple[Any], **kwargs: Dict[str, A...
class GraphQLErrorRateLimit (line 24) | class GraphQLErrorRateLimit(Exception):
class GraphQLErrorTimeout (line 28) | class GraphQLErrorTimeout(Exception):
function get_template (line 32) | def get_template(
function get_query_limit (line 89) | def get_query_limit(access_token: str) -> int:
FILE: backend/src/data/github/graphql/user/contribs/contribs.py
function get_user_contribution_calendar (line 9) | def get_user_contribution_calendar(
function get_user_contribution_events (line 48) | def get_user_contribution_events(
FILE: backend/src/data/github/graphql/user/contribs/models.py
class RawCalendarDay (line 7) | class RawCalendarDay(BaseModel):
class RawCalendarWeek (line 13) | class RawCalendarWeek(BaseModel):
class RawCalendar (line 17) | class RawCalendar(BaseModel):
class RawEventsRepoName (line 21) | class RawEventsRepoName(BaseModel):
class RawEventsCount (line 25) | class RawEventsCount(BaseModel):
class RawEventsCommit (line 29) | class RawEventsCommit(BaseModel):
class RawEventsEvent (line 34) | class RawEventsEvent(BaseModel):
class RawEventsPageInfo (line 38) | class RawEventsPageInfo(BaseModel):
class Config (line 42) | class Config:
class RawEventsCommits (line 46) | class RawEventsCommits(BaseModel):
class RawEventsContribs (line 51) | class RawEventsContribs(BaseModel):
class RawEventsRepoCommits (line 56) | class RawEventsRepoCommits(BaseModel):
class RawEventsRepo (line 62) | class RawEventsRepo(BaseModel):
class RawEventsRepoEvent (line 68) | class RawEventsRepoEvent(BaseModel):
class RawEventsRepoContribs (line 73) | class RawEventsRepoContribs(BaseModel):
class RawEvents (line 78) | class RawEvents(BaseModel):
FILE: backend/src/data/github/graphql/user/follows/follows.py
function get_user_followers (line 8) | def get_user_followers(
function get_user_following (line 66) | def get_user_following(
FILE: backend/src/data/github/graphql/user/follows/models.py
class PageInfo (line 8) | class PageInfo(BaseModel):
class Config (line 12) | class Config:
class RawFollows (line 16) | class RawFollows(BaseModel):
FILE: backend/src/data/github/rest/commit.py
function get_commit_files (line 9) | def get_commit_files(
FILE: backend/src/data/github/rest/models.py
class RawCommit (line 6) | class RawCommit(BaseModel):
class RawCommitFile (line 11) | class RawCommitFile(BaseModel):
FILE: backend/src/data/github/rest/repo.py
function get_repo (line 12) | def get_repo(access_token: str, owner: str, repo: str) -> Dict[str, Any]:
function get_repo_languages (line 24) | def get_repo_languages(
function get_repo_stargazers (line 39) | def get_repo_stargazers(
function get_repo_code_frequency (line 62) | def get_repo_code_frequency(access_token: str, owner: str, repo: str) ->...
function get_repo_commit_activity (line 76) | def get_repo_commit_activity(
function get_repo_contributors (line 92) | def get_repo_contributors(access_token: str, owner: str, repo: str) -> D...
function get_repo_weekly_commits (line 106) | def get_repo_weekly_commits(access_token: str, owner: str, repo: str) ->...
function get_repo_hourly_commits (line 120) | def get_repo_hourly_commits(access_token: str, owner: str, repo: str) ->...
function get_repo_commits (line 132) | def get_repo_commits(
FILE: backend/src/data/github/rest/template.py
class RESTError (line 13) | class RESTError(Exception):
class RESTErrorUnauthorized (line 17) | class RESTErrorUnauthorized(RESTError):
class RESTErrorNotFound (line 21) | class RESTErrorNotFound(RESTError):
class RESTErrorEmptyRepo (line 25) | class RESTErrorEmptyRepo(RESTError):
class RESTErrorTimeout (line 29) | class RESTErrorTimeout(RESTError):
function _get_template (line 33) | def _get_template(
function get_template (line 80) | def get_template(
function get_template_plural (line 95) | def get_template_plural(
FILE: backend/src/data/github/rest/user.py
function get_user (line 8) | def get_user(user_id: str, access_token: str) -> Dict[str, Any]:
function get_user_starred_repos (line 17) | def get_user_starred_repos(
FILE: backend/src/data/github/utils.py
function get_access_token (line 6) | def get_access_token(access_token: Optional[str] = None) -> str:
FILE: backend/src/data/mongo/main.py
function get_conn_str (line 7) | def get_conn_str(password: str, database: str) -> str:
FILE: backend/src/data/mongo/secret/functions.py
function get_keys (line 12) | async def get_keys(no_cache: bool = False) -> Tuple[bool, List[str]]:
function update_keys (line 24) | async def update_keys(no_cache: bool = False) -> None:
function get_random_key (line 29) | def get_random_key() -> str:
FILE: backend/src/data/mongo/secret/models.py
class SecretModel (line 6) | class SecretModel(BaseModel):
FILE: backend/src/data/mongo/user/functions.py
function is_user_key (line 6) | async def is_user_key(user_id: str, user_key: str) -> bool:
function update_user (line 13) | async def update_user(user_id: str, raw_user: Dict[str, Any]) -> None:
function delete_user (line 21) | async def delete_user(user_id: str, user_key: str, use_user_key: bool = ...
FILE: backend/src/data/mongo/user/get.py
function get_public_user (line 11) | async def get_public_user(
function get_full_user (line 25) | async def get_full_user(
FILE: backend/src/data/mongo/user/models.py
class PublicUserModel (line 6) | class PublicUserModel(BaseModel):
class Config (line 11) | class Config:
method set_name (line 16) | def set_name(cls, private_access: Optional[bool]):
class FullUserModel (line 20) | class FullUserModel(PublicUserModel):
FILE: backend/src/data/mongo/user_months/functions.py
function set_user_month (line 5) | async def set_user_month(user_month: UserMonth):
FILE: backend/src/data/mongo/user_months/get.py
function get_user_months (line 10) | async def get_user_months(
FILE: backend/src/data/mongo/user_months/models.py
class UserMonth (line 8) | class UserMonth(BaseModel):
FILE: backend/src/main.py
function read_root (line 57) | async def read_root() -> Dict[str, str]:
function get_info (line 62) | def get_info() -> Dict[str, bool]:
FILE: backend/src/models/background.py
class UpdateUserBackgroundTask (line 7) | class UpdateUserBackgroundTask(BaseModel):
FILE: backend/src/models/svg.py
class LanguageStats (line 6) | class LanguageStats(BaseModel):
class RepoLanguage (line 13) | class RepoLanguage(BaseModel):
class RepoStats (line 19) | class RepoStats(BaseModel):
FILE: backend/src/models/user/contribs.py
class Language (line 7) | class Language(BaseModel):
method compress (line 12) | def compress(self) -> List[Any]:
method decompress (line 16) | def decompress(cls, data: List[Any]) -> "Language":
method __add__ (line 19) | def __add__(self, other: "Language") -> "Language":
class ContributionStats (line 27) | class ContributionStats(BaseModel):
method compress (line 37) | def compress(self) -> List[Any]:
method decompress (line 54) | def decompress(cls, data: List[Any]) -> "ContributionStats":
method __add__ (line 66) | def __add__(self, other: "ContributionStats") -> "ContributionStats":
method empty (line 86) | def empty(cls) -> "ContributionStats":
class ContributionLists (line 99) | class ContributionLists(BaseModel):
method compress (line 106) | def compress(self) -> List[Any]:
method decompress (line 110) | def decompress(cls, data: List[Any]) -> "ContributionLists":
class ContributionDay (line 116) | class ContributionDay(BaseModel):
method compress (line 122) | def compress(self) -> List[Any]:
method decompress (line 131) | def decompress(cls, data: List[Any]) -> "ContributionDay":
class RepoContributionStats (line 140) | class RepoContributionStats(ContributionStats, BaseModel):
method compress (line 151) | def compress(self) -> List[Any]:
method decompress (line 157) | def decompress(cls, data: List[Any]) -> "RepoContributionStats":
method __add__ (line 162) | def __add__( # type: ignore
class UserContributions (line 172) | class UserContributions(BaseModel):
method compress (line 180) | def compress(self) -> List[Any]:
method decompress (line 198) | def decompress(cls, data: List[Any]) -> "UserContributions":
method __add__ (line 219) | def __add__(self, other: "UserContributions") -> "UserContributions":
method trim_contribs (line 247) | def trim_contribs(
method trim (line 277) | def trim(self, start: date, end: date) -> "UserContributions":
method empty (line 302) | def empty(cls) -> "UserContributions":
FILE: backend/src/models/user/follows.py
class User (line 6) | class User(BaseModel):
class Config (line 11) | class Config:
class UserFollows (line 15) | class UserFollows(BaseModel):
FILE: backend/src/models/user/main.py
class UserPackage (line 11) | class UserPackage(BaseModel):
method compress (line 15) | def compress(self):
method decompress (line 21) | def decompress(cls, data: Dict[str, Any]) -> "UserPackage":
method __add__ (line 26) | def __add__(self, other: "UserPackage") -> "UserPackage":
method trim (line 29) | def trim(self, start: date, end: date) -> "UserPackage":
method empty (line 33) | def empty(cls) -> "UserPackage":
FILE: backend/src/models/wrapped/calendar.py
class CalendarLanguageDayDatum (line 6) | class CalendarLanguageDayDatum(BaseModel):
class CalendarDayDatum (line 11) | class CalendarDayDatum(BaseModel):
class CalendarData (line 23) | class CalendarData(BaseModel):
method empty (line 27) | def empty(cls) -> "CalendarData":
FILE: backend/src/models/wrapped/langs.py
class LangDatum (line 6) | class LangDatum(BaseModel):
class LangData (line 14) | class LangData(BaseModel):
method empty (line 19) | def empty(cls) -> "LangData":
FILE: backend/src/models/wrapped/main.py
class WrappedPackage (line 11) | class WrappedPackage(BaseModel):
method empty (line 22) | def empty(cls) -> "WrappedPackage":
FILE: backend/src/models/wrapped/numeric.py
class ContribStats (line 6) | class ContribStats(BaseModel):
method empty (line 15) | def empty(cls) -> "ContribStats":
class MiscStats (line 26) | class MiscStats(BaseModel):
method empty (line 38) | def empty(cls) -> "MiscStats":
class LOCStats (line 52) | class LOCStats(BaseModel):
method empty (line 62) | def empty(cls) -> "LOCStats":
class NumericData (line 74) | class NumericData(BaseModel):
method empty (line 80) | def empty(cls) -> "NumericData":
FILE: backend/src/models/wrapped/repos.py
class RepoDatum (line 6) | class RepoDatum(BaseModel):
class RepoData (line 13) | class RepoData(BaseModel):
method empty (line 18) | def empty(cls) -> "RepoData":
FILE: backend/src/models/wrapped/time.py
class TimeDatum (line 6) | class TimeDatum(BaseModel):
class MonthData (line 13) | class MonthData(BaseModel):
method empty (line 17) | def empty(cls) -> "MonthData":
class DayData (line 21) | class DayData(BaseModel):
method empty (line 25) | def empty(cls) -> "DayData":
FILE: backend/src/models/wrapped/timestamps.py
class TimestampDatum (line 6) | class TimestampDatum(BaseModel):
class TimestampData (line 12) | class TimestampData(BaseModel):
method empty (line 16) | def empty(cls) -> "TimestampData":
FILE: backend/src/processing/auth.py
function set_user_key (line 18) | async def set_user_key(code: str, user_key: str) -> str:
function authenticate (line 23) | async def authenticate(
function delete_user (line 66) | async def delete_user(user_id: str, user_key: str, use_user_key: bool = ...
FILE: backend/src/processing/user/commits.py
function loc_metric_func (line 10) | def loc_metric_func(loc_metric: str, additions: int, deletions: int) -> ...
function get_top_languages (line 16) | def get_top_languages(
function get_top_repos (line 60) | def get_top_repos(
FILE: backend/src/processing/user/svg.py
function svg_base (line 10) | async def svg_base(
FILE: backend/src/processing/wrapped/calendar.py
function get_calendar_data (line 7) | def get_calendar_data(data: UserPackage, year: int) -> CalendarData:
FILE: backend/src/processing/wrapped/langs.py
function _count_loc (line 8) | def _count_loc(x: Language, metric: str) -> int:
function get_lang_data (line 14) | def get_lang_data(data: UserPackage) -> LangData:
FILE: backend/src/processing/wrapped/main.py
function query_wrapped_user (line 12) | async def query_wrapped_user(
FILE: backend/src/processing/wrapped/numeric.py
function get_contrib_stats (line 8) | def get_contrib_stats(data: UserPackage) -> ContribStats:
function get_misc_stats (line 21) | def get_misc_stats(data: UserPackage, year: int) -> MiscStats:
function format_loc_number (line 83) | def format_loc_number(number: int) -> str:
function get_loc_stats (line 91) | def get_loc_stats(data: UserPackage) -> LOCStats:
function get_numeric_data (line 122) | def get_numeric_data(data: UserPackage, year: int) -> NumericData:
FILE: backend/src/processing/wrapped/package.py
function get_wrapped_data (line 12) | def get_wrapped_data(user_package: UserPackage, year: int) -> WrappedPac...
FILE: backend/src/processing/wrapped/repos.py
function _count_loc (line 7) | def _count_loc(x: Language, metric: str) -> int:
function _count_repo_loc (line 13) | def _count_repo_loc(x: RepoContributionStats, metric: str) -> int:
function get_repo_data (line 17) | def get_repo_data(data: UserPackage) -> RepoData:
FILE: backend/src/processing/wrapped/time.py
function get_month_data (line 9) | def get_month_data(data: UserPackage) -> MonthData:
function get_day_data (line 37) | def get_day_data(data: UserPackage) -> DayData:
FILE: backend/src/processing/wrapped/timestamps.py
function date_to_seconds_since_midnight (line 10) | def date_to_seconds_since_midnight(date: datetime) -> int:
function get_timestamp_data (line 14) | def get_timestamp_data(data: UserPackage) -> TimestampData:
FILE: backend/src/render/error.py
function get_error_svg (line 12) | def get_error_svg() -> Drawing:
function get_empty_demo_svg (line 43) | def get_empty_demo_svg(header: str) -> Drawing:
function get_loading_svg (line 76) | def get_loading_svg() -> Drawing:
function get_no_data_svg (line 111) | def get_no_data_svg(header: str, subheader: str) -> Drawing:
FILE: backend/src/render/style.py
function get_style (line 55) | def get_style(theme: str = "classic", use_animation: bool = True) -> str:
FILE: backend/src/render/template.py
function get_template (line 13) | def get_template(
function get_bar_section (line 55) | def get_bar_section(
function get_lang_name_section (line 111) | def get_lang_name_section(
FILE: backend/src/render/top_langs.py
function get_top_langs_svg (line 13) | def get_top_langs_svg(
FILE: backend/src/render/top_repos.py
function get_top_repos_svg (line 14) | def get_top_repos_svg(
FILE: backend/src/routers/assets/assets.py
function get_error_img (line 8) | async def get_error_img():
function get_stopwatch_img (line 13) | async def get_stopwatch_img():
FILE: backend/src/routers/auth/standalone.py
function redirect_public (line 15) | def redirect_public(user_id: Optional[str] = None) -> RedirectResponse:
function redirect_private (line 20) | def redirect_private(user_id: Optional[str] = None) -> RedirectResponse:
function redirect_return (line 25) | async def redirect_return(code: str = "", private_access: bool = False) ...
function delete_account_auth (line 35) | async def delete_account_auth(user_id: str) -> RedirectResponse:
function delete_account (line 42) | async def delete_account(user_id: str) -> RedirectResponse:
FILE: backend/src/routers/auth/website.py
function set_user_key_endpoint (line 21) | async def set_user_key_endpoint(response: Response, code: str, user_key:...
function authenticate_endpoint (line 32) | async def authenticate_endpoint(
function delete_user_endpoint (line 52) | async def delete_user_endpoint(response: Response, user_id: str, user_ke...
FILE: backend/src/routers/background.py
function run_in_background (line 10) | async def run_in_background(task: UpdateUserBackgroundTask):
FILE: backend/src/routers/decorators.py
function get_redirect_url (line 16) | def get_redirect_url(
function svg_fail_gracefully (line 45) | def svg_fail_gracefully(func: Callable[..., Any]):
FILE: backend/src/routers/dev.py
function get_user_raw (line 19) | async def get_user_raw(
function get_wrapped_user_raw (line 40) | async def get_wrapped_user_raw(
FILE: backend/src/routers/users/db.py
function update_keys_endpoint (line 19) | async def update_keys_endpoint(response: Response) -> bool:
function get_db_public_user (line 31) | async def get_db_public_user(
FILE: backend/src/routers/users/main.py
function get_user_endpoint (line 25) | async def get_user_endpoint(
FILE: backend/src/routers/users/svg.py
function get_user_lang_svg (line 25) | async def get_user_lang_svg(
function get_user_repo_svg (line 72) | async def get_user_repo_svg(
function get_demo_svg (line 121) | async def get_demo_svg(response: Response, card: str) -> Any:
FILE: backend/src/routers/wrapped.py
function check_valid_user (line 17) | async def check_valid_user(response: Response, user_id: str) -> str:
function get_wrapped_user (line 23) | async def get_wrapped_user(
FILE: backend/src/utils/alru_cache.py
function alru_cache (line 21) | def alru_cache(max_size: int = 128, ttl: timedelta = timedelta(minutes=1)):
FILE: backend/src/utils/decorators.py
function fail_gracefully (line 9) | def fail_gracefully(func: Callable[..., Any]):
function async_fail_gracefully (line 28) | def async_fail_gracefully(func: Callable[..., Any]):
FILE: backend/src/utils/gather.py
function async_function (line 6) | def async_function(func: Callable[..., Any]) -> Callable[..., Any]:
function gather_with_concurrency (line 16) | async def gather_with_concurrency(
function gather (line 28) | async def gather(
FILE: backend/src/utils/utils.py
function date_to_datetime (line 5) | def date_to_datetime(
function use_time_range (line 12) | def use_time_range(
function format_number (line 37) | def format_number(num: int) -> str:
FILE: backend/tests/aggregation/layer0/test_contributions.py
class TestTemplate (line 10) | class TestTemplate(AsyncTestCase):
method test_get_contributions (line 11) | async def test_get_contributions(self):
FILE: backend/tests/aggregation/layer0/test_follows.py
class TestTemplate (line 8) | class TestTemplate(unittest.TestCase):
method test_get_follows (line 9) | def test_get_follows(self):
FILE: backend/tests/data/github/auth/test_main.py
class TestTemplate (line 7) | class TestTemplate(unittest.TestCase):
method test_get_unknown_user_valid (line 8) | def test_get_unknown_user_valid(self):
method test_get_unknown_user_invalid (line 12) | def test_get_unknown_user_invalid(self):
FILE: backend/tests/data/github/graphql/test_commits.py
class TestTemplate (line 7) | class TestTemplate(unittest.TestCase):
method test_get_commits (line 8) | def test_get_commits(self):
method test_get_commits_invalid_access_token (line 17) | def test_get_commits_invalid_access_token(self):
method test_get_commits_invalid_node_ids (line 25) | def test_get_commits_invalid_node_ids(self):
FILE: backend/tests/data/github/graphql/test_repo.py
class TestTemplate (line 11) | class TestTemplate(unittest.TestCase):
method test_get_repo (line 12) | def test_get_repo(self):
method test_get_repo_invalid_access_token (line 23) | def test_get_repo_invalid_access_token(self):
method test_get_commits_invalid_args (line 29) | def test_get_commits_invalid_args(self):
FILE: backend/tests/data/github/graphql/test_user_contribs.py
class TestTemplate (line 13) | class TestTemplate(unittest.TestCase):
method test_get_user_contribution_calendar (line 14) | def test_get_user_contribution_calendar(self):
method test_get_user_contribution_events (line 24) | def test_get_user_contribution_events(self):
FILE: backend/tests/data/github/graphql/test_user_follows.py
class TestTemplate (line 7) | class TestTemplate(unittest.TestCase):
method test_get_user_followers (line 8) | def test_get_user_followers(self):
method test_get_user_following (line 15) | def test_get_user_following(self):
FILE: backend/tests/data/github/rest/test_commit.py
class TestTemplate (line 12) | class TestTemplate(unittest.TestCase):
method test_get_commit_files (line 13) | def test_get_commit_files(self):
method test_get_commit_files_invalid_access_token (line 20) | def test_get_commit_files_invalid_access_token(self):
method test_get_commit_files_invalid_args (line 24) | def test_get_commit_files_invalid_args(self):
FILE: backend/tests/data/github/rest/test_repo.py
class TestTemplate (line 11) | class TestTemplate(unittest.TestCase):
method test_get_repo_commits (line 12) | def test_get_repo_commits(self):
method test_get_repo_commits_invalid_access_token (line 17) | def test_get_repo_commits_invalid_access_token(self):
method test_get_repo_commits_invalid_args (line 21) | def test_get_repo_commits_invalid_args(self):
FILE: backend/tests/utils/test_alru_cache.py
class TestTemplate (line 10) | class TestTemplate(AsyncTestCase):
method test_basic_alru_cache (line 11) | async def test_basic_alru_cache(self):
method test_alru_cache_with_flag (line 32) | async def test_alru_cache_with_flag(self):
method test_alru_cache_with_maxsize (line 53) | async def test_alru_cache_with_maxsize(self):
method test_alru_cache_with_ttl (line 72) | async def test_alru_cache_with_ttl(self):
method test_alru_cache_with_no_cache (line 90) | async def test_alru_cache_with_no_cache(self):
FILE: frontend/src/api/user.js
constant URL_PREFIX (line 6) | const URL_PREFIX = BACKEND_URL;
FILE: frontend/src/api/wrapped.js
constant URL_PREFIX (line 7) | const URL_PREFIX = `${BACKEND_URL}/wrapped`;
FILE: frontend/src/constants.js
constant PROD (line 2) | const PROD = process.env.REACT_APP_PROD === 'true';
constant USE_LOGGER (line 4) | const USE_LOGGER = true;
constant CLIENT_ID (line 6) | const CLIENT_ID = PROD
constant MODE (line 10) | const MODE = process.env.REACT_APP_MODE;
constant REDIRECT_URI (line 12) | const REDIRECT_URI = PROD
constant GITHUB_PRIVATE_AUTH_URL (line 20) | const GITHUB_PRIVATE_AUTH_URL = `https://github.com/login/oauth/authoriz...
constant GITHUB_PUBLIC_AUTH_URL (line 21) | const GITHUB_PUBLIC_AUTH_URL = `https://github.com/login/oauth/authorize...
constant WRAPPED_URL (line 23) | const WRAPPED_URL = PROD
constant BACKEND_URL (line 27) | const BACKEND_URL = PROD
constant CURR_YEAR (line 31) | const CURR_YEAR = 2024;
FILE: frontend/src/pages/App/AppTrends.js
function WrappedAuthRedirectScreen (line 26) | function WrappedAuthRedirectScreen() {
function WrappedRedirectScreen (line 37) | function WrappedRedirectScreen() {
function App (line 53) | function App() {
FILE: frontend/src/pages/App/AppWrapped.js
function App (line 17) | function App() {
FILE: frontend/src/pages/App/Footer.js
function Footer (line 4) | function Footer() {
FILE: frontend/src/pages/Home/Home.js
function redirectCode (line 98) | async function redirectCode() {
FILE: frontend/src/pages/Landing/Landing.js
function LandingScreen (line 19) | function LandingScreen() {
FILE: frontend/src/pages/Misc/Redirect.js
function redirectCode (line 7) | async function redirectCode() {
FILE: frontend/src/pages/Settings/Settings.js
function useOutsideAlerter (line 41) | function useOutsideAlerter(ref, action) {
FILE: frontend/src/pages/Wrapped/SelectUser.js
function redirectCode (line 39) | async function redirectCode() {
FILE: frontend/src/pages/Wrapped/Wrapped.js
function getData (line 59) | async function getData() {
FILE: frontend/src/redux/actions/userActions.js
constant LOGIN (line 1) | const LOGIN = 'LOGIN';
constant LOGOUT (line 2) | const LOGOUT = 'LOGOUT';
constant SET_PRIVATE_ACCESS (line 3) | const SET_PRIVATE_ACCESS = 'SET_PRIVATE_ACCESS';
function login (line 5) | function login(userId, userKey) {
function logout (line 9) | function logout() {
function setPrivateAccess (line 13) | function setPrivateAccess(privateAccess) {
FILE: frontend/src/redux/store.js
function configureStore (line 7) | function configureStore(intialState) {
FILE: frontend/src/utils.js
function sleep (line 1) | function sleep(ms) {
function classnames (line 6) | function classnames(...args) {
Condensed preview — 238 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (451K chars).
[
{
"path": ".gitattributes",
"chars": 66,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
},
{
"path": ".github/workflows/backend.yaml",
"chars": 1007,
"preview": "name: CI-Backend\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\njobs:\n build:\n runs-on: ubu"
},
{
"path": ".github/workflows/frontend.yaml",
"chars": 330,
"preview": "name: CI-Frontend\n\non:\n push:\n branches: [ main ]\n pull_request:\n branches: [ main ]\n\njobs:\n build:\n runs-on"
},
{
"path": ".gitignore",
"chars": 122,
"preview": "*.pyc\n__pycache__\n\n.vscode\n\nbackend/.env\nbackend/.venv\nbackend/.coverage\nbackend/gcloud_key.json\n\nfrontend/.env\n.DS_Stor"
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2020 Abhijit Gupta\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 2826,
"preview": "# GitHub Trends\n\n## SPECIAL: GitHub Wrapped\n\nCheck out your GitHub Wrapped at `githubwrapped.io`!\n\n == \"True\"\nPROD = os.getenv(\"PROD\", \"False\") == \"True\"\nPROJECT_ID"
},
{
"path": "backend/src/data/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/data/github/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/data/github/auth/__init__.py",
"chars": 79,
"preview": "from src.data.github.auth.main import authenticate\n\n__all__ = [\"authenticate\"]\n"
},
{
"path": "backend/src/data/github/auth/main.py",
"chars": 1645,
"preview": "from datetime import datetime\nfrom typing import Dict, Optional, Tuple\n\nimport requests\n\nfrom src.constants import OAUTH"
},
{
"path": "backend/src/data/github/extensions.json",
"chars": 62468,
"preview": "{\n \".1\": {\n \"color\": \"#ecdebe\",\n \"name\": \"Roff Manpage\"\n },\n \".1in\": {\n \"color\": \"#ecdebe\""
},
{
"path": "backend/src/data/github/graphql/__init__.py",
"chars": 1178,
"preview": "from src.data.github.graphql.commit import get_commits\nfrom src.data.github.graphql.models import RawCommit, RawRepo\nfro"
},
{
"path": "backend/src/data/github/graphql/commit.py",
"chars": 2993,
"preview": "from typing import List, Optional\n\nfrom src.constants import PR_FILES\nfrom src.data.github.graphql.models import RawComm"
},
{
"path": "backend/src/data/github/graphql/models.py",
"chars": 1195,
"preview": "from typing import List, Optional\n\nfrom pydantic import BaseModel, Field\n\n\nclass RawCommitPRFileNode(BaseModel):\n pat"
},
{
"path": "backend/src/data/github/graphql/repo.py",
"chars": 1385,
"preview": "from typing import Optional\n\nfrom src.data.github.graphql.models import RawRepo\nfrom src.data.github.graphql.template im"
},
{
"path": "backend/src/data/github/graphql/template.py",
"chars": 3231,
"preview": "import logging\nfrom datetime import datetime\nfrom typing import Any, Dict, Optional, Tuple\n\nimport requests\nfrom request"
},
{
"path": "backend/src/data/github/graphql/user/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/data/github/graphql/user/contribs/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/data/github/graphql/user/contribs/contribs.py",
"chars": 5791,
"preview": "# import json\nfrom datetime import datetime\nfrom typing import Optional\n\nfrom src.data.github.graphql.template import ge"
},
{
"path": "backend/src/data/github/graphql/user/contribs/models.py",
"chars": 2507,
"preview": "from datetime import date, datetime\nfrom typing import List, Optional\n\nfrom pydantic import BaseModel, Field\n\n\nclass Raw"
},
{
"path": "backend/src/data/github/graphql/user/follows/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/data/github/graphql/user/follows/follows.py",
"chars": 3332,
"preview": "# import json\nfrom typing import Dict, Optional, Union\n\nfrom src.data.github.graphql.template import get_template\nfrom s"
},
{
"path": "backend/src/data/github/graphql/user/follows/models.py",
"chars": 389,
"preview": "from typing import List, Optional\n\nfrom pydantic import BaseModel, Field\n\nfrom src.models import User\n\n\nclass PageInfo(B"
},
{
"path": "backend/src/data/github/language_map.py",
"chars": 1130,
"preview": "import json\nimport urllib.request\nfrom typing import Any, Dict\n\nBLACKLIST = [\".md\"]\n\nwith urllib.request.urlopen(\n \"h"
},
{
"path": "backend/src/data/github/rest/__init__.py",
"chars": 556,
"preview": "from src.data.github.rest.commit import get_commit_files\nfrom src.data.github.rest.models import RawCommit, RawCommitFil"
},
{
"path": "backend/src/data/github/rest/commit.py",
"chars": 802,
"preview": "from typing import List, Optional\n\nfrom src.data.github.rest.models import RawCommitFile\nfrom src.data.github.rest.templ"
},
{
"path": "backend/src/data/github/rest/models.py",
"chars": 223,
"preview": "from datetime import datetime\n\nfrom pydantic import BaseModel\n\n\nclass RawCommit(BaseModel):\n timestamp: datetime\n "
},
{
"path": "backend/src/data/github/rest/repo.py",
"chars": 5418,
"preview": "import logging\nfrom datetime import datetime\nfrom typing import Any, Dict, List, Optional\n\nfrom src.data.github.rest.mod"
},
{
"path": "backend/src/data/github/rest/template.py",
"chars": 3370,
"preview": "from datetime import datetime\nfrom typing import Any, Dict, List, Optional\n\nimport requests\nfrom requests.exceptions imp"
},
{
"path": "backend/src/data/github/rest/user.py",
"chars": 926,
"preview": "from typing import Any, Dict, List\n\nfrom src.data.github.rest.template import get_template, get_template_plural\n\nBASE_UR"
},
{
"path": "backend/src/data/github/utils.py",
"chars": 219,
"preview": "from typing import Optional\n\nfrom src.data.mongo.secret import get_random_key\n\n\ndef get_access_token(access_token: Optio"
},
{
"path": "backend/src/data/mongo/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/data/mongo/main.py",
"chars": 1095,
"preview": "from motor.core import AgnosticCollection\nfrom motor.motor_asyncio import AsyncIOMotorClient\n\nfrom src.constants import "
},
{
"path": "backend/src/data/mongo/secret/__init__.py",
"chars": 117,
"preview": "from src.data.mongo.secret.functions import get_random_key, update_keys\n\n__all__ = [\"get_random_key\", \"update_keys\"]\n"
},
{
"path": "backend/src/data/mongo/secret/functions.py",
"chars": 940,
"preview": "from datetime import timedelta\nfrom random import randint\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom src."
},
{
"path": "backend/src/data/mongo/secret/models.py",
"chars": 134,
"preview": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass SecretModel(BaseModel):\n project: str\n access_toke"
},
{
"path": "backend/src/data/mongo/user/__init__.py",
"chars": 377,
"preview": "from src.data.mongo.user.functions import delete_user, is_user_key, update_user\nfrom src.data.mongo.user.get import get_"
},
{
"path": "backend/src/data/mongo/user/functions.py",
"chars": 784,
"preview": "from typing import Any, Dict, Optional\n\nfrom src.data.mongo.main import USERS\n\n\nasync def is_user_key(user_id: str, user"
},
{
"path": "backend/src/data/mongo/user/get.py",
"chars": 1112,
"preview": "from typing import Any, Dict, Optional, Tuple\n\nfrom pydantic import ValidationError\n\nfrom src.data.mongo.main import USE"
},
{
"path": "backend/src/data/mongo/user/models.py",
"chars": 513,
"preview": "from typing import Optional\n\nfrom pydantic import BaseModel, validator\n\n\nclass PublicUserModel(BaseModel):\n user_id: "
},
{
"path": "backend/src/data/mongo/user_months/__init__.py",
"chars": 241,
"preview": "from src.data.mongo.user_months.functions import set_user_month\nfrom src.data.mongo.user_months.get import get_user_mont"
},
{
"path": "backend/src/data/mongo/user_months/functions.py",
"chars": 437,
"preview": "from src.data.mongo.main import USER_MONTHS\nfrom src.data.mongo.user_months.models import UserMonth\n\n\nasync def set_user"
},
{
"path": "backend/src/data/mongo/user_months/get.py",
"chars": 1662,
"preview": "from datetime import date, datetime\nfrom typing import Any, Dict, List\n\nfrom src.constants import API_VERSION, USER_WHIT"
},
{
"path": "backend/src/data/mongo/user_months/models.py",
"chars": 243,
"preview": "from datetime import datetime\n\nfrom pydantic import BaseModel\n\nfrom src.models import UserPackage\n\n\nclass UserMonth(Base"
},
{
"path": "backend/src/main.py",
"chars": 1494,
"preview": "from typing import Dict\n\nimport sentry_sdk\nfrom dotenv import find_dotenv, load_dotenv\nfrom fastapi import FastAPI\nfrom "
},
{
"path": "backend/src/models/__init__.py",
"chars": 1214,
"preview": "from src.models.user.contribs import (\n ContributionDay,\n Language,\n RepoContributionStats,\n UserContributio"
},
{
"path": "backend/src/models/background.py",
"chars": 265,
"preview": "from datetime import date\nfrom typing import Optional\n\nfrom pydantic import BaseModel\n\n\nclass UpdateUserBackgroundTask(B"
},
{
"path": "backend/src/models/svg.py",
"chars": 361,
"preview": "from typing import List, Optional\n\nfrom pydantic import BaseModel\n\n\nclass LanguageStats(BaseModel):\n lang: str\n lo"
},
{
"path": "backend/src/models/user/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/models/user/contribs.py",
"chars": 10706,
"preview": "from datetime import date, datetime\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom pydantic import BaseModel\n"
},
{
"path": "backend/src/models/user/follows.py",
"chars": 272,
"preview": "from typing import List, Optional\n\nfrom pydantic import BaseModel\n\n\nclass User(BaseModel):\n name: Optional[str]\n l"
},
{
"path": "backend/src/models/user/main.py",
"chars": 943,
"preview": "from datetime import date\nfrom typing import Any, Dict\n\nfrom pydantic import BaseModel\n\nfrom src.models.user.contribs im"
},
{
"path": "backend/src/models/wrapped/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/models/wrapped/calendar.py",
"chars": 528,
"preview": "from typing import Dict, List\n\nfrom pydantic import BaseModel\n\n\nclass CalendarLanguageDayDatum(BaseModel):\n loc_added"
},
{
"path": "backend/src/models/wrapped/langs.py",
"chars": 375,
"preview": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass LangDatum(BaseModel):\n id: str\n label: str\n val"
},
{
"path": "backend/src/models/wrapped/main.py",
"chars": 998,
"preview": "from pydantic import BaseModel\n\nfrom src.models.wrapped.calendar import CalendarData\nfrom src.models.wrapped.langs impor"
},
{
"path": "backend/src/models/wrapped/numeric.py",
"chars": 1959,
"preview": "from typing import Optional, Tuple\n\nfrom pydantic import BaseModel\n\n\nclass ContribStats(BaseModel):\n contribs: int\n "
},
{
"path": "backend/src/models/wrapped/repos.py",
"chars": 360,
"preview": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass RepoDatum(BaseModel):\n id: int\n label: str\n val"
},
{
"path": "backend/src/models/wrapped/time.py",
"chars": 455,
"preview": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass TimeDatum(BaseModel):\n index: int\n contribs: int\n "
},
{
"path": "backend/src/models/wrapped/timestamps.py",
"chars": 309,
"preview": "from typing import List\n\nfrom pydantic import BaseModel\n\n\nclass TimestampDatum(BaseModel):\n type: str\n weekday: in"
},
{
"path": "backend/src/processing/auth.py",
"chars": 2266,
"preview": "from typing import Any, Dict, Optional, Tuple\n\nfrom src.data.github.auth import authenticate as github_authenticate\nfrom"
},
{
"path": "backend/src/processing/user/__init__.py",
"chars": 180,
"preview": "from src.processing.user.commits import get_top_languages, get_top_repos\nfrom src.processing.user.svg import svg_base\n\n_"
},
{
"path": "backend/src/processing/user/commits.py",
"chars": 4844,
"preview": "from typing import Any, Dict, List, Optional, Tuple, Union\n\nfrom src.constants import DEFAULT_COLOR\nfrom src.models impo"
},
{
"path": "backend/src/processing/user/svg.py",
"chars": 1105,
"preview": "from datetime import date\nfrom typing import Optional, Tuple\n\nfrom src.aggregation.layer2.user import get_user, get_user"
},
{
"path": "backend/src/processing/wrapped/__init__.py",
"chars": 93,
"preview": "from src.processing.wrapped.main import query_wrapped_user\n\n__all__ = [\"query_wrapped_user\"]\n"
},
{
"path": "backend/src/processing/wrapped/calendar.py",
"chars": 1847,
"preview": "from datetime import datetime, timedelta\nfrom typing import Any, Dict, List\n\nfrom src.models import CalendarData, Calend"
},
{
"path": "backend/src/processing/wrapped/langs.py",
"chars": 1466,
"preview": "from typing import List\n\nfrom src.constants import DEFAULT_COLOR\nfrom src.models import LangData, LangDatum, Language, U"
},
{
"path": "backend/src/processing/wrapped/main.py",
"chars": 1176,
"preview": "from datetime import date, timedelta\nfrom typing import Optional, Tuple\n\nfrom src.aggregation.layer1 import query_user\nf"
},
{
"path": "backend/src/processing/wrapped/numeric.py",
"chars": 4771,
"preview": "from collections import defaultdict\nfrom datetime import datetime\nfrom typing import Dict\n\nfrom src.models import Contri"
},
{
"path": "backend/src/processing/wrapped/package.py",
"chars": 1266,
"preview": "from src.models import UserPackage, WrappedPackage\nfrom src.processing.wrapped.calendar import get_calendar_data\nfrom sr"
},
{
"path": "backend/src/processing/wrapped/repos.py",
"chars": 1619,
"preview": "from typing import List\n\nfrom src.models import Language, RepoContributionStats, RepoData, RepoDatum, UserPackage\nfrom s"
},
{
"path": "backend/src/processing/wrapped/time.py",
"chars": 1833,
"preview": "from collections import defaultdict\nfrom datetime import datetime\nfrom typing import Dict, List, Union\n\nfrom src.models "
},
{
"path": "backend/src/processing/wrapped/timestamps.py",
"chars": 993,
"preview": "from datetime import datetime\nfrom random import shuffle\nfrom typing import Any, List\n\nfrom src.models import TimestampD"
},
{
"path": "backend/src/render/__init__.py",
"chars": 378,
"preview": "from src.render.error import (\n get_empty_demo_svg,\n get_error_svg,\n get_loading_svg,\n get_no_data_svg,\n)\nfr"
},
{
"path": "backend/src/render/error.py",
"chars": 2879,
"preview": "# type: ignore\n\nfrom svgwrite import Drawing\n\nfrom src.constants import BACKEND_URL\nfrom src.render.style import styles_"
},
{
"path": "backend/src/render/style.py",
"chars": 3421,
"preview": "from typing import List, Tuple\n\nthemes = {\n \"classic\": {\n \"header_color\": \"#2f80ed\",\n \"subheader_color\""
},
{
"path": "backend/src/render/template.py",
"chars": 4034,
"preview": "# type: ignore\n\nfrom typing import List, Tuple\n\nfrom svgwrite import Drawing\nfrom svgwrite.container import Group\nfrom s"
},
{
"path": "backend/src/render/top_langs.py",
"chars": 2195,
"preview": "# type: ignore\n\nfrom typing import List, Tuple\n\nfrom svgwrite import Drawing\n\nfrom src.models.svg import LanguageStats\nf"
},
{
"path": "backend/src/render/top_repos.py",
"chars": 2189,
"preview": "# type: ignore\n\nfrom collections import defaultdict\nfrom typing import List, Tuple\n\nfrom svgwrite import Drawing\n\nfrom s"
},
{
"path": "backend/src/routers/__init__.py",
"chars": 370,
"preview": "from src.routers.assets.assets import router as asset_router\nfrom src.routers.auth.main import router as auth_router\nfro"
},
{
"path": "backend/src/routers/assets/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/routers/assets/assets.py",
"chars": 461,
"preview": "from fastapi import APIRouter, status\nfrom fastapi.responses import FileResponse\n\nrouter = APIRouter()\n\n\n@router.get(\"/e"
},
{
"path": "backend/src/routers/auth/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/routers/auth/main.py",
"chars": 288,
"preview": "from fastapi import APIRouter\n\nfrom src.routers.auth.standalone import router as standalone_router\nfrom src.routers.auth"
},
{
"path": "backend/src/routers/auth/standalone.py",
"chars": 1580,
"preview": "import logging\nfrom typing import Optional\n\nfrom fastapi import APIRouter\nfrom fastapi.responses import RedirectResponse"
},
{
"path": "backend/src/routers/auth/website.py",
"chars": 1534,
"preview": "from typing import Any, Dict\n\nfrom fastapi import BackgroundTasks, status\nfrom fastapi.responses import Response\nfrom fa"
},
{
"path": "backend/src/routers/background.py",
"chars": 1010,
"preview": "from typing import Dict\n\nfrom src.aggregation.layer1 import query_user\nfrom src.models.background import UpdateUserBackg"
},
{
"path": "backend/src/routers/decorators.py",
"chars": 2343,
"preview": "import io\nimport logging\nfrom datetime import datetime\nfrom functools import wraps\nfrom typing import Any, Callable, Dic"
},
{
"path": "backend/src/routers/dev.py",
"chars": 1535,
"preview": "from datetime import date, timedelta\nfrom typing import Any, Dict, Optional\n\nfrom fastapi import APIRouter, Response, st"
},
{
"path": "backend/src/routers/users/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/src/routers/users/db.py",
"chars": 1336,
"preview": "from typing import Any, Dict, Optional\n\nfrom fastapi import APIRouter, Response, status\n\nfrom src.data.mongo.secret impo"
},
{
"path": "backend/src/routers/users/main.py",
"chars": 1265,
"preview": "from datetime import date, timedelta\nfrom typing import Any, Dict, Optional\n\nfrom fastapi import APIRouter, BackgroundTa"
},
{
"path": "backend/src/routers/users/svg.py",
"chars": 3777,
"preview": "from datetime import date, timedelta\nfrom typing import Any\n\nfrom fastapi import BackgroundTasks, Response, status\nfrom "
},
{
"path": "backend/src/routers/wrapped.py",
"chars": 989,
"preview": "from typing import Any, Dict, Optional\n\nfrom fastapi import APIRouter, Response, status\n\nfrom src.aggregation.layer2 imp"
},
{
"path": "backend/src/utils/__init__.py",
"chars": 394,
"preview": "from src.utils.alru_cache import alru_cache\nfrom src.utils.decorators import async_fail_gracefully, fail_gracefully\nfrom"
},
{
"path": "backend/src/utils/alru_cache.py",
"chars": 2372,
"preview": "from datetime import datetime, timedelta\nfrom functools import wraps\nfrom typing import (\n Any,\n Awaitable,\n Ca"
},
{
"path": "backend/src/utils/decorators.py",
"chars": 1580,
"preview": "import logging\nfrom datetime import datetime\nfrom functools import wraps\nfrom typing import Any, Callable, Dict, List\n\nf"
},
{
"path": "backend/src/utils/gather.py",
"chars": 1187,
"preview": "import asyncio\nfrom functools import partial, wraps\nfrom typing import Any, Callable, Dict, List\n\n\ndef async_function(fu"
},
{
"path": "backend/src/utils/utils.py",
"chars": 1436,
"preview": "from datetime import date, datetime, timedelta\nfrom typing import Tuple\n\n\ndef date_to_datetime(\n dt: date, hour: int "
},
{
"path": "backend/tests/__init__.py",
"chars": 86,
"preview": "from dotenv import find_dotenv, load_dotenv\n\nload_dotenv(find_dotenv(), verbose=True)\n"
},
{
"path": "backend/tests/aggregation/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/aggregation/layer0/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/aggregation/layer0/test_contributions.py",
"chars": 649,
"preview": "from datetime import date, timedelta\n\nfrom aiounittest.case import AsyncTestCase\n\nfrom src.aggregation.layer0.contributi"
},
{
"path": "backend/tests/aggregation/layer0/test_follows.py",
"chars": 401,
"preview": "import unittest\n\nfrom src.aggregation.layer0.follows import get_user_follows\nfrom src.constants import TEST_TOKEN as TOK"
},
{
"path": "backend/tests/data/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/data/github/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/data/github/auth/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/data/github/auth/test_main.py",
"chars": 470,
"preview": "import unittest\n\nfrom src.constants import TEST_TOKEN as TOKEN, TEST_USER_ID as USER_ID\nfrom src.data.github.auth.main i"
},
{
"path": "backend/tests/data/github/graphql/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/data/github/graphql/test_commits.py",
"chars": 1225,
"preview": "import unittest\n\nfrom src.constants import TEST_NODE_IDS as NODE_IDS, TEST_TOKEN as TOKEN\nfrom src.data.github.graphql i"
},
{
"path": "backend/tests/data/github/graphql/test_repo.py",
"chars": 1050,
"preview": "import unittest\n\nfrom src.constants import (\n TEST_REPO as REPO,\n TEST_TOKEN as TOKEN,\n TEST_USER_ID as USER_ID"
},
{
"path": "backend/tests/data/github/graphql/test_user_contribs.py",
"chars": 955,
"preview": "import unittest\nfrom datetime import datetime, timedelta\n\nfrom src.constants import TEST_TOKEN as TOKEN, TEST_USER_ID as"
},
{
"path": "backend/tests/data/github/graphql/test_user_follows.py",
"chars": 825,
"preview": "import unittest\n\nfrom src.constants import TEST_TOKEN as TOKEN, TEST_USER_ID as USER_ID\nfrom src.data.github.graphql imp"
},
{
"path": "backend/tests/data/github/rest/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/data/github/rest/test_commit.py",
"chars": 874,
"preview": "import unittest\n\nfrom src.constants import (\n TEST_REPO as REPO,\n TEST_SHA as SHA,\n TEST_TOKEN as TOKEN,\n TE"
},
{
"path": "backend/tests/data/github/rest/test_repo.py",
"chars": 776,
"preview": "import unittest\n\nfrom src.constants import (\n TEST_REPO as REPO,\n TEST_TOKEN as TOKEN,\n TEST_USER_ID as USER_ID"
},
{
"path": "backend/tests/utils/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "backend/tests/utils/test_alru_cache.py",
"chars": 2859,
"preview": "from asyncio import sleep\nfrom datetime import timedelta\nfrom typing import Tuple\n\nfrom aiounittest.case import AsyncTes"
},
{
"path": "backend/transfer_mongodb.bash",
"chars": 2117,
"preview": "if [ $# -eq 0 ]; then\n echo \"Usage: $0 <mongodb_password>\"\n exit 1\nfi\n\n# Export\nmongoexport --uri \"mongodb+srv://b"
},
{
"path": "docs/API.md",
"chars": 6506,
"preview": "# GitHub Trends API\n\nGitHub Trends provides two methods to access GitHub Trends data: the Website Workflow at githubtren"
},
{
"path": "docs/CONTRIBUTING.md",
"chars": 1099,
"preview": "# GitHub Trends\n\nIf you are interested in contributing to GitHub Trends, take a look through the codebase and at the ope"
},
{
"path": "docs/FAQ.md",
"chars": 2242,
"preview": "# FAQ\n\nThe FAQ is in progress. Reach out if you have any unanswered questions or concerns.\n\n---\n\n**Question**: Does GitH"
},
{
"path": "docs/THEME.md",
"chars": 1521,
"preview": "The following themes are available for all GitHub Trends cards:\n\n| Themes "
},
{
"path": "frontend/.env-template",
"chars": 48,
"preview": "REACT_APP_PROD=false\n\nREACT_APP_CLIENT_ID=abc123"
},
{
"path": "frontend/.eslintrc.js",
"chars": 748,
"preview": "module.exports = {\n env: {\n browser: true,\n es6: true,\n },\n extends: ['airbnb', 'plugin:prettier/recommended'],"
},
{
"path": "frontend/.gitignore",
"chars": 337,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "frontend/.prettierrc.js",
"chars": 114,
"preview": "module.exports = {\n semi: true,\n trailingComma: 'all',\n singleQuote: true,\n printWidth: 80,\n tabWidth: 2,\n};\n"
},
{
"path": "frontend/.yarnrc",
"chars": 22,
"preview": "network-timeout 500000"
},
{
"path": "frontend/README.md",
"chars": 299,
"preview": "# Frontend\n\n## Installation\n\n```\nyarn install\n```\n\n## Run Locally\n\n```\nyarn start-trends\nyarn start-wrapped\n```\n\n## Buil"
},
{
"path": "frontend/deploy/Dockerfile",
"chars": 213,
"preview": "FROM node:16-alpine\n\nWORKDIR /frontend\n\nENV PATH /frontend/node_modules/.bin:$PATH\n\nCOPY ../package.json ../yarn.lock /f"
},
{
"path": "frontend/package.json",
"chars": 2300,
"preview": "{\n \"name\": \"frontend\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": {\n \"@babel/runtime\": \"^7.23.2\",\n "
},
{
"path": "frontend/public/_redirects",
"chars": 18,
"preview": "/* /index.html 200"
},
{
"path": "frontend/public/manifest.json",
"chars": 492,
"preview": "{\n \"short_name\": \"React App\",\n \"name\": \"Create React App Sample\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n "
},
{
"path": "frontend/public/robots.txt",
"chars": 67,
"preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
},
{
"path": "frontend/public/trends.html",
"chars": 2921,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.i"
},
{
"path": "frontend/public/wrapped.html",
"chars": 2901,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.i"
},
{
"path": "frontend/src/api/index.js",
"chars": 215,
"preview": "import {\n setUserKey,\n authenticate,\n getUserMetadata,\n deleteAccount,\n} from './user';\n\nimport { getWrapped } from "
},
{
"path": "frontend/src/api/user.js",
"chars": 1330,
"preview": "import axios from 'axios';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { BACKEND_URL } from '../constants';\n\nconst URL_"
},
{
"path": "frontend/src/api/wrapped.js",
"chars": 647,
"preview": "/* eslint-disable no-return-await */\n\nimport axios from 'axios';\n\nimport { BACKEND_URL } from '../constants';\n\nconst URL"
},
{
"path": "frontend/src/assets/notes.txt",
"chars": 145,
"preview": "2022: Generated laptop mockups using deviceshots.com\n2023: Used https://www.anthonyboyd.graphics/mockups/m2-macbook-air-"
},
{
"path": "frontend/src/components/Card/Card.js",
"chars": 1530,
"preview": "import React from 'react';\nimport { useSelector } from 'react-redux';\nimport PropTypes from 'prop-types';\n\nimport { BACK"
},
{
"path": "frontend/src/components/Card/SVG.js",
"chars": 1664,
"preview": "/* eslint-disable react/jsx-props-no-spreading */\n/* eslint-disable react/no-danger */\n\nimport React, { useEffect, useSt"
},
{
"path": "frontend/src/components/Card/index.js",
"chars": 105,
"preview": "import SvgInline from './SVG';\nimport { Card, Image } from './Card';\n\nexport { Card, Image, SvgInline };\n"
},
{
"path": "frontend/src/components/Generic/Button.js",
"chars": 603,
"preview": "/* eslint-disable react/jsx-props-no-spreading */\nimport React from 'react';\nimport PropTypes from 'prop-types';\n\nimport"
},
{
"path": "frontend/src/components/Generic/Checkbox.js",
"chars": 882,
"preview": "/* eslint-disable jsx-a11y/interactive-supports-focus */\n/* eslint-disable jsx-a11y/click-events-have-key-events */\n\nimp"
},
{
"path": "frontend/src/components/Generic/Input.js",
"chars": 1533,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { classnames } from '../../utils';\n\n// options is"
},
{
"path": "frontend/src/components/Generic/index.js",
"chars": 132,
"preview": "import Button from './Button';\nimport Checkbox from './Checkbox';\nimport Input from './Input';\n\nexport { Button, Checkbo"
},
{
"path": "frontend/src/components/Home/CheckboxSection.js",
"chars": 930,
"preview": "/* eslint-disable jsx-a11y/interactive-supports-focus */\n/* eslint-disable jsx-a11y/click-events-have-key-events */\n\nimp"
},
{
"path": "frontend/src/components/Home/DateRangeSection.js",
"chars": 1342,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Section from './Section';\nimport { Input } from '"
},
{
"path": "frontend/src/components/Home/Progress.js",
"chars": 2343,
"preview": "/* eslint-disable react/no-array-index-key */\nimport React from 'react';\nimport PropTypes from 'prop-types';\n\nimport {\n "
},
{
"path": "frontend/src/components/Home/Section.js",
"chars": 1029,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { HiOutlineLightningBolt as LightningIcon } from "
},
{
"path": "frontend/src/components/Home/index.js",
"chars": 198,
"preview": "import ProgressBar from './Progress';\nimport CheckboxSection from './CheckboxSection';\nimport DateRangeSection from './D"
},
{
"path": "frontend/src/components/Preview/Preview.js",
"chars": 1735,
"preview": "import React, { useState, useEffect } from 'react';\nimport PropTypes from 'prop-types';\n\nimport {\n FaArrowRight as Arro"
},
{
"path": "frontend/src/components/Preview/index.js",
"chars": 58,
"preview": "import Preview from './Preview';\n\nexport default Preview;\n"
},
{
"path": "frontend/src/components/Wrapped/Organization.js",
"chars": 1322,
"preview": "/* eslint-disable jsx-a11y/mouse-events-have-key-events */\n\nimport React from 'react';\nimport PropTypes from 'prop-types"
},
{
"path": "frontend/src/components/Wrapped/Specifics/Bar.js",
"chars": 3749,
"preview": "import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\n\nimport { WrappedCard } from '../Organizati"
},
{
"path": "frontend/src/components/Wrapped/Specifics/Calendar.js",
"chars": 3476,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { ResponsiveCalendar } from '@nivo/calendar';\n\nim"
},
{
"path": "frontend/src/components/Wrapped/Specifics/Numeric.js",
"chars": 2847,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { WrappedCard } from '../Organization';\n\nconst nu"
},
{
"path": "frontend/src/components/Wrapped/Specifics/Pie.js",
"chars": 3040,
"preview": "/* eslint-disable react/jsx-curly-newline */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { P"
},
{
"path": "frontend/src/components/Wrapped/Specifics/Radar.js",
"chars": 1439,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { ResponsiveRadar } from '@nivo/radar';\n\nimport {"
},
{
"path": "frontend/src/components/Wrapped/Specifics/Swarm.js",
"chars": 1124,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { SwarmPlot } from '../Templates';\n\nconst formatY"
},
{
"path": "frontend/src/components/Wrapped/Specifics/index.js",
"chars": 155,
"preview": "import Calendar from './Calendar';\n\nexport * from './Bar';\nexport * from './Numeric';\nexport * from './Pie';\nexport * fr"
},
{
"path": "frontend/src/components/Wrapped/Templates/Bar.js",
"chars": 1937,
"preview": "/* eslint-disable react/jsx-curly-newline */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { R"
},
{
"path": "frontend/src/components/Wrapped/Templates/Numeric.js",
"chars": 2388,
"preview": "/* eslint-disable jsx-a11y/mouse-events-have-key-events */\n\nimport React from 'react';\nimport PropTypes from 'prop-types"
},
{
"path": "frontend/src/components/Wrapped/Templates/Pie.js",
"chars": 1858,
"preview": "/* eslint-disable react/jsx-curly-newline */\n\nimport React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { R"
},
{
"path": "frontend/src/components/Wrapped/Templates/Swarm.js",
"chars": 2698,
"preview": "/* eslint-disable react/prop-types */\n/* eslint-disable react/jsx-curly-newline */\n\nimport React from 'react';\nimport Pr"
},
{
"path": "frontend/src/components/Wrapped/Templates/index.js",
"chars": 188,
"preview": "import BarGraph from './Bar';\nimport PieChart from './Pie';\nimport SwarmPlot from './Swarm';\n\nexport * from './Numeric';"
},
{
"path": "frontend/src/components/Wrapped/Templates/theme.js",
"chars": 349,
"preview": "export const theme = {\n fontSize: '12px',\n fontFamily: 'Segoe UI',\n};\n\nexport const scale = ['#EBEDF0', '#9BE9A8', '#4"
},
{
"path": "frontend/src/components/Wrapped/index.js",
"chars": 90,
"preview": "export * from './Organization';\nexport * from './Templates';\nexport * from './Specifics';\n"
},
{
"path": "frontend/src/components/index.js",
"chars": 157,
"preview": "import Preview from './Preview';\n\nexport * from './Generic';\nexport * from './Card';\nexport * from './Home';\nexport * fr"
},
{
"path": "frontend/src/constants.js",
"chars": 1062,
"preview": "/* eslint-disable no-nested-ternary */\nexport const PROD = process.env.REACT_APP_PROD === 'true';\n\nexport const USE_LOGG"
},
{
"path": "frontend/src/index.css",
"chars": 228,
"preview": "/* ./src/index.css */\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nbody {\n margin: 0;\n font-family: 'Se"
}
]
// ... and 38 more files (download for full content)
About this extraction
This page contains the full source code of the avgupta456/github-trends GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 238 files (399.1 KB), approximately 112.0k tokens, and a symbol index with 335 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.