*/ private readonly array $allowedHosts; public function __construct( #[TaggedIterator('app.provider')] private readonly iterable $providers, private readonly SvgRenderer $renderer, private readonly CacheInterface $cache, private readonly LoggerInterface $logger, #[Autowire(env: 'ALLOWED_HOSTS')] string $allowedHosts = '', ) { $this->allowedHosts = array_values(array_filter(array_map('trim', explode(',', $allowedHosts)))); } /** * Query parameters: * theme string dark|light (default: dark) */ #[Route('/graph.svg', name: 'contribution_graph', methods: ['GET'])] public function graph(Request $request): Response { if ($this->allowedHosts !== [] && !in_array($request->getHost(), $this->allowedHosts, true)) { return new Response('Forbidden', 403); } $theme = $request->query->get('theme', 'dark'); $cacheKey = 'graph_' . $theme; $svg = $this->cache->get($cacheKey, function (ItemInterface $item) use ($theme): string { $item->expiresAfter(3600); return $this->renderer->render($this->fetchAllContributions(), $theme); }); return new Response($svg, 200, [ 'Content-Type' => 'image/svg+xml', 'Cache-Control' => 'public, max-age=3600', ]); } #[Route('/', name: 'index', methods: ['GET'])] public function index(Request $request): RedirectResponse { $query = $request->query->all(); $url = '/graph.svg' . ($query ? '?' . http_build_query($query) : ''); return new RedirectResponse($url, 302); } #[Route('/health', name: 'health', methods: ['GET'])] public function health(): Response { return new Response('{"status":"ok"}', 200, ['Content-Type' => 'application/json']); } /** @return array */ private function fetchAllContributions(): array { $contributions = []; /** @var ProviderInterface $provider */ foreach ($this->providers as $provider) { if (!$provider->isConfigured()) { continue; } try { $contributions = $this->merge($contributions, $provider->fetch()); } catch (\Throwable $e) { $this->logger->warning(sprintf('%s fetch failed: %s', $provider::class, $e->getMessage())); } } return $contributions; } /** @param array $base @param array $new @return array */ private function merge(array $base, array $new): array { foreach ($new as $date => $count) { $base[$date] = ($base[$date] ?? 0) + $count; } return $base; } }