From cf7bd8d70c74f26caf21f9280818e71d7c245c3f Mon Sep 17 00:00:00 2001 From: ArthurErlich Date: Sun, 12 Jul 2026 23:43:59 +0200 Subject: [PATCH] docs(readme): correct outdated deploy docs and docker-compose examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 80 ++++++++++++++++++++++++++++++++++------- docker-compose.prod.yml | 3 ++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index af3053f..026ff10 100644 --- a/README.md +++ b/README.md @@ -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 (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 (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, 1–3 → 1, 4–6 → 2, 7–9 → 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. --- diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index cccefcb..ce08665 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -18,9 +18,11 @@ services: GITEA_USER: "${GITEA_USER:-}" GITEA_TOKEN: "${GITEA_TOKEN:-}" GITEA_URL: "${GITEA_URL:-}" + CONTRIBUTIONS_RETENTION_DAYS: "${CONTRIBUTIONS_RETENTION_DAYS:-0}" volumes: - cache:/app/var/cache/prod/pools - logs:/app/var/log + - data:/app/var/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s @@ -30,3 +32,4 @@ services: volumes: cache: logs: + data: