docs(readme): correct outdated deploy docs and docker-compose examples

README had drifted from the codebase: wrong test path, a stale /health
sample response, a docker-compose volume mount pointing at a path the
image never writes to (with no SQLite data volume at all), a missing
CONTRIBUTIONS_RETENTION_DAYS example, and an architecture diagram
predating the SQLite store/refetch command. Also fixes
docker-compose.prod.yml, which was missing the data volume and
CONTRIBUTIONS_RETENTION_DAYS env var present in docker-compose.yml —
without it, a prod deployment on this file loses contribution history
on every container recreate.
This commit is contained in:
2026-07-12 23:43:59 +02:00
parent de831beaaa
commit cf7bd8d70c
2 changed files with 71 additions and 12 deletions
+68 -12
View File
@@ -40,10 +40,14 @@ services:
env_file:
- .env.local
volumes:
- cache:/var/www/html/var/cache
- cache:/app/var/cache/prod/pools
- logs:/app/var/log
- data:/app/var/data
volumes:
cache:
logs:
data:
```
The image is published to the Gitea container registry. Pull it manually with:
@@ -77,6 +81,9 @@ GITEA_URL=https://git.example.com
# Optional: restrict to specific hostnames (comma-separated), leave empty to allow all
ALLOWED_HOSTS=
# Optional: days of contribution history to keep in the SQLite store, empty/0 = keep forever
CONTRIBUTIONS_RETENTION_DAYS=
```
Only configure the platforms you use — unused ones are silently skipped.
@@ -87,15 +94,26 @@ Only configure the platforms you use — unused ones are silently skipped.
docker compose up -d
```
The service listens on **port 8080** by default. Put Traefik or nginx in front of it for HTTPS.
The service listens on **port 8080** by default (served by FrankenPHP/Caddy, baked into the image — no separate PHP-FPM/nginx needed). Put Traefik or nginx in front of it only if you need HTTPS termination.
### 4. Verify
```bash
curl http://localhost:8080/health
# {"status":"ok"}
```
```json
{
"status": "ok",
"providers": {
"github": { "status": "ok" },
"gitlab": { "status": "ok" }
}
}
```
Returns HTTP 503 with `"status": "degraded"` if any configured provider's probe fails (its entry then includes `error` and `message`).
---
## API
@@ -201,28 +219,43 @@ vendor/bin/phpunit
vendor/bin/phpunit --testdox
# Single file
vendor/bin/phpunit tests/Unit/Service/SvgRendererTest.php
vendor/bin/phpunit tests/Unit/Service/Renderer/SvgRendererTest.php
# Filter by name
vendor/bin/phpunit --filter it_renders
```
### Static analysis
```bash
composer phpstan
```
Runs PHPStan at level 8 over `src/` and `tests/` (see `phpstan.neon`). First
run's findings are catalogued in [PHP-Stan-Errors.md](PHP-Stan-Errors.md).
---
## Architecture
Two-tier cache: a 1h filesystem SVG cache in front of a SQLite raw-data store, in front of the provider APIs.
```
GET /graph.svg?theme=dark|light
└─ GraphController
├─ host check (ALLOWED_HOSTS env, optional)
├─ cache lookup (filesystem, 1h TTL, key = "graph_{theme}")
│ └─ on miss:
│ ├─ GitHubProvider → GitHub GraphQL API (contributionCalendar)
├─ GitLabProvider → GitLab REST API (/users/:id/events, paginated)
─ GiteaProvider → Gitea REST API (/users/:user/heatmap)
each returns array<string, int> (Y-m-d => count)
failures are caught and logged; remaining providers still render
└─ merge by date (sum counts across providers)
│ └─ on miss: ContributionAggregator::aggregate()
│ ├─ per configured provider: ContributionStore::latestDate($name)
│ → $since = latest - 3 days (trailing overlap), or null on first run
─ GitHubProvider → GitHub GraphQL API (contributionCalendar query, bounded by $since/$until)
├─ GitLabProvider → GitLab REST /users/:id/events (paginated, 100/page, `after`/`before` bounded)
├─ GiteaProvider → Gitea REST /api/v1/users/:user/heatmap (filtered client-side by $since/$until)
│ each returns array<string, int> (Y-m-d => count); providers fetch concurrently
│ │ (start/resolve split); failures are caught and logged, remaining providers still render
│ ├─ ContributionStore::merge() persists fresh data per provider (SQLite upsert)
│ ├─ ContributionStore::all(sinceDays: 371) reads back the render window (source of truth)
│ ├─ ContributionStore::prune() drops rows older than CONTRIBUTIONS_RETENTION_DAYS
│ └─ SvgRenderer::render()
└─ Response: image/svg+xml, Cache-Control: public max-age=3600
```
@@ -231,7 +264,30 @@ GET /graph.svg?theme=dark|light
**SvgRenderer:** builds a 53-column × 7-row grid aligned so the last column always ends on the Saturday of the current week. Five intensity levels (0 → level 0, 13 → 1, 46 → 2, 79 → 3, 10+ → 4) mapped to GitHub's colour tokens. No external assets — the SVG is fully self-contained.
**Cache:** filesystem adapter (`var/cache/`), mounted as a Docker volume to survive container restarts. Theme is part of the cache key so dark and light are cached independently.
**SVG cache:** filesystem adapter (`var/cache/`), mounted as a Docker volume to survive container restarts. Theme is part of the cache key so dark and light are cached independently.
**ContributionStore:** PDO SQLite at `var/data/contributions.db` (also a Docker volume), keyed by `(provider, date)`. Bounds every re-fetch to a trailing window off the last stored date per provider, instead of re-pulling all 365 days on every cache miss. `CONTRIBUTIONS_RETENTION_DAYS` controls how far back rows are kept (empty/0 = forever).
**Health check:** `GET /health` probes each configured provider's credentials/reachability (without fetching contribution data) and reports `ok`/`degraded`.
---
## CLI
`graph:contributions:refetch` is a manual escape hatch that bypasses the incremental trailing-window fetch and re-pulls a full or explicit date range — useful for backfilling history or recovering from a gap.
```bash
docker compose exec graph bin/console graph:contributions:refetch --all
```
| Option | Default | Description |
| ------------ | -------------------- | --------------------------------------------------------- |
| `--provider` | all configured | Comma-separated provider names to refetch, e.g. `github,gitlab` |
| `--from` | 365 days ago | Start date (`YYYY-MM-DD`) |
| `--to` | today | End date (`YYYY-MM-DD`) |
| `--all` | off | Shorthand for `--from=2005-01-01` |
Large ranges are split into ≤365-day chunks and merged into the store as each completes.
---