From a083311cf0c0154de562e88124993d10a865fa29 Mon Sep 17 00:00:00 2001 From: butschster Date: Sun, 21 Apr 2024 22:07:13 +0400 Subject: [PATCH] Adds issue section --- .../Bootloader/GithubBootloader.php | 2 + .../IssuesForContributorsAction.php | 14 ++++- .../Http/Resource/IssueCollection.php | 4 +- .../Endpoint/Http/Resource/IssueResource.php | 4 +- app/app/src/Github/CacheableClient.php | 13 +++++ app/app/src/Github/Client.php | 51 +++++++++++++++++-- app/app/src/Github/ClientInterface.php | 7 +++ app/app/src/Github/Entity/Issue.php | 25 ++++++++- docker-compose.yaml | 2 + spa/components/v1/Contribution.vue | 6 +-- 10 files changed, 116 insertions(+), 12 deletions(-) diff --git a/app/app/src/Application/Bootloader/GithubBootloader.php b/app/app/src/Application/Bootloader/GithubBootloader.php index cdc28b0..9e84e9c 100644 --- a/app/app/src/Application/Bootloader/GithubBootloader.php +++ b/app/app/src/Application/Bootloader/GithubBootloader.php @@ -19,12 +19,14 @@ public function defineSingletons(): array return [ ClientInterface::class => static fn( CacheStorageProviderInterface $cache, + EnvironmentInterface $env, ) => new CacheableClient( client: new Client( client: new \GuzzleHttp\Client([ 'base_uri' => 'https://api.github.com/', 'headers' => [ 'Accept' => 'application/vnd.github.v3+json', + 'Authorization' => 'Bearer ' . $env->get('GITHUB_TOKEN'), ], ]), ), diff --git a/app/app/src/Endpoint/Http/Controller/IssuesForContributorsAction.php b/app/app/src/Endpoint/Http/Controller/IssuesForContributorsAction.php index 464380d..9a79ee8 100644 --- a/app/app/src/Endpoint/Http/Controller/IssuesForContributorsAction.php +++ b/app/app/src/Endpoint/Http/Controller/IssuesForContributorsAction.php @@ -4,7 +4,17 @@ namespace App\Endpoint\Http\Controller; -final class IssuesForContributorsAction -{ +use App\Application\Http\Response\ResourceInterface; +use App\Endpoint\Http\Resource\IssueCollection; +use App\Endpoint\Http\Resource\IssueResource; +use App\Github\ClientInterface; +use Spiral\Router\Annotation\Route; +final readonly class IssuesForContributorsAction +{ + #[Route(route: 'issues/for-contributors', name: 'issues-for-contributors', methods: ['GET'], group: 'api')] + public function __invoke(ClientInterface $client): ResourceInterface + { + return new IssueCollection($client->getIssuesForContributors(), IssueResource::class); + } } diff --git a/app/app/src/Endpoint/Http/Resource/IssueCollection.php b/app/app/src/Endpoint/Http/Resource/IssueCollection.php index 39661de..a408868 100644 --- a/app/app/src/Endpoint/Http/Resource/IssueCollection.php +++ b/app/app/src/Endpoint/Http/Resource/IssueCollection.php @@ -4,7 +4,9 @@ namespace App\Endpoint\Http\Resource; -final class IssueCollection +use App\Application\Http\Response\ResourceCollection; + +final class IssueCollection extends ResourceCollection { } diff --git a/app/app/src/Endpoint/Http/Resource/IssueResource.php b/app/app/src/Endpoint/Http/Resource/IssueResource.php index af463d5..d43b9ca 100644 --- a/app/app/src/Endpoint/Http/Resource/IssueResource.php +++ b/app/app/src/Endpoint/Http/Resource/IssueResource.php @@ -4,7 +4,9 @@ namespace App\Endpoint\Http\Resource; -final class IssueResource +use App\Application\Http\Response\JsonResource; + +final class IssueResource extends JsonResource { } diff --git a/app/app/src/Github/CacheableClient.php b/app/app/src/Github/CacheableClient.php index 5b492a0..a3f79c6 100644 --- a/app/app/src/Github/CacheableClient.php +++ b/app/app/src/Github/CacheableClient.php @@ -43,6 +43,19 @@ public function getLastVersion(string $repository): string return $version; } + public function getIssuesForContributors(): array + { + $cacheKey = $this->getCacheKey('issues', __METHOD__); + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + + $issues = $this->client->getIssuesForContributors(); + $this->cache->set($cacheKey, $issues, $this->ttl); + + return $issues; + } + public function clearCache(): void { $this->cache->clear(); diff --git a/app/app/src/Github/Client.php b/app/app/src/Github/Client.php index 848c205..e3cfe9b 100644 --- a/app/app/src/Github/Client.php +++ b/app/app/src/Github/Client.php @@ -4,6 +4,8 @@ namespace App\Github; +use App\Github\Entity\Issue; +use Carbon\Carbon; use GuzzleHttp\Psr7\Request; final readonly class Client implements ClientInterface @@ -17,8 +19,8 @@ public function getStars(string $repository): int { $response = $this->client->sendRequest( new Request( - 'GET', - "repos/{$repository}", + method: 'GET', + uri: "repos/{$repository}", ), ); @@ -31,8 +33,8 @@ public function getLastVersion(string $repository): string { $response = $this->client->sendRequest( new Request( - 'GET', - "repos/{$repository}/releases/latest", + method: 'GET', + uri: "repos/{$repository}/releases/latest", ), ); @@ -40,4 +42,45 @@ public function getLastVersion(string $repository): string return $data['tag_name']; } + + public function getIssuesForContributors(): array + { + $repositories = [ + 'buggregator/server', + 'buggregator/trap', + 'buggregator/frontend', + 'buggregator/docs', + ]; + + $issues = []; + + foreach ($repositories as $repository) { + $issues = [...$issues, ...$this->fetchRepositoryIssues($repository)]; + } + + return $issues; + } + + private function fetchRepositoryIssues(string $repository): array + { + $response = $this->client->sendRequest( + new Request( + method: 'GET', + uri: "repos/{$repository}/issues?labels=for%20contributors&assignee=none", + ), + ); + + $data = \json_decode($response->getBody()->getContents(), true); + + return \array_map( + static fn(array $issue): Issue => new Issue( + repository: $repository, + title: $issue['title'], + labels: \array_map(static fn(array $label) => $label['name'], $issue['labels']), + url: $issue['html_url'], + createdAt: Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $issue['created_at']), + ), + $data, + ); + } } diff --git a/app/app/src/Github/ClientInterface.php b/app/app/src/Github/ClientInterface.php index ffd50db..eefa210 100644 --- a/app/app/src/Github/ClientInterface.php +++ b/app/app/src/Github/ClientInterface.php @@ -4,9 +4,16 @@ namespace App\Github; +use App\Github\Entity\Issue; + interface ClientInterface { public function getStars(string $repository): int; public function getLastVersion(string $repository): string; + + /** + * @return Issue[] + */ + public function getIssuesForContributors(): array; } diff --git a/app/app/src/Github/Entity/Issue.php b/app/app/src/Github/Entity/Issue.php index a7b8bc5..fe6d367 100644 --- a/app/app/src/Github/Entity/Issue.php +++ b/app/app/src/Github/Entity/Issue.php @@ -4,7 +4,30 @@ namespace App\Github\Entity; -final class Issue +final readonly class Issue implements \JsonSerializable { + /** + * @param non-empty-string $title + * @param non-empty-string[] $labels + * @param non-empty-string $url + */ + public function __construct( + public string $repository, + public string $title, + public array $labels, + public string $url, + public \DateTimeInterface $createdAt, + ) { + } + public function jsonSerialize(): array + { + return [ + 'repository' => $this->repository, + 'title' => $this->title, + 'labels' => $this->labels, + 'url' => $this->url, + 'createdAt' => $this->createdAt->format(\DateTimeInterface::ATOM), + ]; + } } diff --git a/docker-compose.yaml b/docker-compose.yaml index 2c767e2..e685d9b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -50,6 +50,8 @@ services: VAR_DUMPER_FORMAT: server VAR_DUMPER_SERVER: buggregator-demo:9912 + + GITHUB_TOKEN: ${GITHUB_TOKEN} volumes: - ./app:/app depends_on: diff --git a/spa/components/v1/Contribution.vue b/spa/components/v1/Contribution.vue index 88d7b17..f4f607b 100644 --- a/spa/components/v1/Contribution.vue +++ b/spa/components/v1/Contribution.vue @@ -2,10 +2,10 @@ import { useIssuesStore } from "~/stores/issues"; import GridRow from "~/components/v1/GridRow.vue"; -const issues = computed(() => { - const store = useIssuesStore(); - store.fetch(); +const store = useIssuesStore(); +store.fetch(); +const issues = computed(() => { return store.issues; });