# git-activity-merge Small self-hosted FastAPI service that merges GitHub + Gitea contribution data and returns embeddable contribution heatmap images. Example for GitHub profile README: ```md ![Activity](https://example.com/activity.svg) ``` ## What It Does - Fetches contribution data from: - GitHub GraphQL API (`GITHUB_USERNAME`, optional `GITHUB_TOKEN`) - Gitea heatmap API (`{GITEA_BASE_URL}/api/v1/users/{GITEA_USERNAME}/heatmap`) - Normalizes into date-based counts: - `github` - `gitea` - `total` - Caches source responses and rendered images on disk. - Returns: - merged JSON - GitHub-style SVG heatmap - PNG heatmap ## API Routes - `GET /health` - `GET /activity.json` - `GET /activity.svg` - `GET /activity.png` Supported query params: - `year=YYYY` or `days=365` (mutually exclusive) - `theme=dark|light` - `source=all|github|gitea` Examples: ```bash curl "http://localhost:8000/activity.json?days=365&source=all" curl "http://localhost:8000/activity.svg?year=2026&theme=dark" curl "http://localhost:8000/activity.png?days=180&source=gitea" ``` ## Setup ### 1. Install ```bash python -m venv .venv . .venv/bin/activate # or .venv\Scripts\activate on Windows pip install -e ".[dev]" ``` ### 2. Configure Copy `.env.example` to `.env` and edit values. Required: - `GITHUB_USERNAME` - `GITEA_BASE_URL` - `GITEA_USERNAME` Optional: - `GITHUB_TOKEN` - `GITEA_TOKEN` - `CACHE_TTL_SECONDS` and `CACHE_DIR` enable caching when both are set - `DEFAULT_THEME` (`light` or `dark`, default: `light`) - `SERVICE_TITLE` (default: `git-activity-merge`) If you leave `CACHE_TTL_SECONDS` and `CACHE_DIR` undefined, the app skips caching entirely. ### 3. Run ```bash uvicorn app.main:app --reload --port 8000 ``` ## Docker Build and run the container image directly: ```bash docker build -t registry.reversed.dev/registry/git-activity-merger:latest . docker run --rm -p 8000:8000 \ --env-file .env \ registry.reversed.dev/registry/git-activity-merger:latest ``` Or use Compose: ```bash docker compose up --build ``` Container serves on port `8000`. Compose mounts a named cache volume at `/app/cache`. ## Reverse Proxy Example (Nginx) ```nginx server { server_name example.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` Then embed directly: ```md ![Activity](https://example.com/activity.svg?days=365&theme=dark) ``` ## Notes - GitHub GraphQL without token is possible but lower-rate/less reliable. Set `GITHUB_TOKEN` for stability. - Gitea instances may require `GITEA_TOKEN` for private activity or stricter rate limits. - If upstream APIs fail and stale cache exists, `/activity.json` includes `"stale": true` and stale data is served.