aggregate() now fires every configured provider's startFetch() in one
pass before resolving any of them, so wall-clock cost is roughly
max(providers) instead of sum(providers). Partial-failure isolation
and per-date summation behavior are unchanged.
startFetch() resolves the user id and fires the first events page,
returning a handle without reading it; resolveFetch() continues the
existing do/while pagination starting from that first response.
Pagination itself stays sequential within this provider since each
page depends on the previous one - parallelism here only spans across
providers, not within GitLab's own pages.
startFetch() fires the GraphQL POST and returns the response without
reading it; resolveFetch() does the .toArray()/parse/log work that
used to happen inline right after the request.
Splits the provider contract into a non-blocking startFetch() that
fires the HTTP request(s) and a resolveFetch() that reads the result,
so callers can fire every provider's request before blocking on any
of them. Symfony HttpClient is already async under the hood -
request() doesn't block until you read the response - so this needs
no new dependency.
Providers were moved into the App\Service\Provider namespace, but
services.yaml still tagged/configured them under the old App\Service
FQCNs, so the _instanceof conditional never matched and the container
failed to compile.
Move all provider-related classes, enums, interface and trait into
App\Service\Provider; move SvgRenderer into App\Service\Renderer.
ContributionAggregator stays at the Service root as the orchestrator.
Test namespaces and use statements updated to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multi-size favicon.ico (16×16, 32×32, 48×48) and favicon.png built from
a 5×5 GitHub-green contribution graph grid on a dark #0d1117 background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove composer.lock from .gitignore so it is tracked and included in
the build context; without it composer install resolves fresh versions
on every CI build, causing cache/vendor mismatches that produce the
RewindableGenerator and LazyGhostTrait runtime errors in prod
- Install the intl PHP extension in the base stage to remove the
Symfony startup deprecation warning; icu-dev and libzip-dev are only
kept for the compile step, then deleted to keep the layer lean
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Run cache:warmup in the build stage so containers start with a
pre-built Symfony kernel and DI container
- Scope the cache volume to var/cache/prod/pools where Symfony writes
pool data, preserving the warmed kernel across container restarts
- Add docker-compose.prod.yml for deploying the registry image without
the dev override being picked up automatically
- Expand .dockerignore to exclude vendor/, tests/, docs, compose files,
and env files from the build context
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix "Prever" typo, add Windows WSL test runner command, and align
Markdown table columns for readability.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce ProviderHealthChecker which probes each configured provider
via AutowireIterator. Wire it into GraphController so /health returns
detailed per-provider status and responds 503 when any provider is
in a degraded state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add getName() and ping() to each provider and wire ProbeTrait. Make
all classes final, add strict_types declarations, and replace generic
RuntimeException with typed HTTP exceptions so probe() can classify
auth failures and unreachable endpoints correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add getName(), ping(), and probe() to ProviderInterface. Introduce
ProbeTrait to classify HTTP/transport exceptions into typed error codes,
ProviderStatus as the result value object, and ProviderStatusType /
ProviderErrorCode as backing enums.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace setUp() factory with direct instantiation (zero-param constructor)
- Add AAA blank-line separators to all test methods
- Consolidate theme + fallback tests behind a DataProvider
- Consolidate day-of-week label tests behind a DataProvider
- Split multi-assertion tests to one assertion per test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move fetchAllContributions and merge logic from GraphController into a new
ContributionAggregator service. Replace deprecated TaggedIterator with
AutowireIterator throughout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The PostToolUse hook called php bin/phpunit directly, which does not
work inside the Docker-based dev setup. Remove it; tests should be
run via docker compose exec graph vendor/bin/phpunit as documented.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the clone-and-build workflow with a docker-compose snippet
that pulls the image directly from the Gitea container registry.
Add semver tag reference and rename steps to match the new flow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Declare empty-string defaults for all optional env vars as Symfony
parameters so the container compiles without errors when GITHUB_USER,
GITLAB_TOKEN, GITEA_URL, etc. are not set in the environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace php:8.4-cli-alpine3.21 with dunglas/frankenphp:1-php8.4-alpine.
No Go toolchain required — the official image ships as a pre-compiled
Alpine binary.
- Drop docker-php-ext-install opcache (FrankenPHP enables it by default)
- Both dev and final stages run frankenphp run --config /etc/caddy/Caddyfile
- Dev stage bakes in Caddyfile.dev (no worker mode, Xdebug compatible)
- Final stage bakes in production Caddyfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>