diff --git a/src/VCS/Adapter.php b/src/VCS/Adapter.php index 2487908..da01f4a 100644 --- a/src/VCS/Adapter.php +++ b/src/VCS/Adapter.php @@ -86,6 +86,7 @@ abstract public function getOwnerName(string $installationId): string; /** * Search repositories for GitHub App + * @param string $installationId ID of the installation * @param string $owner Name of user or org * @param int $page page number * @param int $per_page number of results per page @@ -94,7 +95,7 @@ abstract public function getOwnerName(string $installationId): string; * * @throws Exception */ - abstract public function searchRepositories(string $owner, int $page, int $per_page, string $search = ''): array; + abstract public function searchRepositories(string $installationId, string $owner, int $page, int $per_page, string $search = ''): array; /** * Get repository diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index fdc4929..75a4658 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -96,6 +96,7 @@ public function createRepository(string $owner, string $repositoryName, bool $pr /** * Search repositories for GitHub App + * @param string $installationId ID of the installation * @param string $owner Name of user or org * @param int $page page number * @param int $per_page number of results per page @@ -104,24 +105,87 @@ public function createRepository(string $owner, string $repositoryName, bool $pr * * @throws Exception */ - public function searchRepositories(string $owner, int $page, int $per_page, string $search = ''): array + public function searchRepositories(string $installationId, string $owner, int $page, int $per_page, string $search = ''): array { - $url = '/search/repositories'; + // Find whether installation has access to all (or) specific repositories + $url = '/app/installations/' . $installationId; + $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->jwtToken"]); + $hasAccessToAllRepositories = ($response['body']['repository_selection'] ?? '') === 'all'; + + // Installation has access to all repositories, use the search API which supports filtering. + if ($hasAccessToAllRepositories) { + $url = '/search/repositories'; + + $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [ + 'q' => "{$search} user:{$owner} fork:true", + 'page' => $page, + 'per_page' => $per_page, + 'sort' => 'updated' + ]); + + if (!isset($response['body']['items'])) { + throw new Exception("Repositories list missing in the response."); + } + + return [ + 'items' => $response['body']['items'], + 'total' => $response['body']['total_count'], + ]; + } - $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [ - 'q' => "{$search} user:{$owner} fork:true", - 'page' => $page, - 'per_page' => $per_page, - 'sort' => 'updated' - ]); + // Installation has access to specific repositories, we need to perform client-side filtering. + $url = '/installation/repositories'; + $repositories = []; - if (!isset($response['body']['items'])) { - throw new Exception("Repositories list missing in the response."); + // When no search query is provided, delegate pagination to the GitHub API. + if (empty($search)) { + $repositories = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [ + 'page' => $page, + 'per_page' => $per_page, + ]); + + if (!isset($repositories['body']['repositories'])) { + throw new Exception("Repositories list missing in the response."); + } + + return [ + 'items' => $repositories['body']['repositories'], + 'total' => $repositories['body']['total_count'], + ]; } + // When search query is provided, fetch all repositories accessible by the installation and filter them locally. + $currentPage = 1; + while (true) { + $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [ + 'page' => $currentPage, + 'per_page' => 100, // Maximum allowed by GitHub API + ]); + + if (!isset($response['body']['repositories'])) { + throw new Exception("Repositories list missing in the response."); + } + + // Filter repositories to only include those that match the search query. + $filteredRepositories = array_filter($response['body']['repositories'], fn ($repo) => stripos($repo['name'], $search) !== false); + + // Merge with result so far. + $repositories = array_merge($repositories, $filteredRepositories); + + // If less than 100 repositories are returned, we have fetched all repositories. + if (\count($response['body']['repositories']) < 100) { + break; + } + + // Increment page number to fetch next page. + $currentPage++; + } + + $repositoriesInRequestedPage = \array_slice($repositories, ($page - 1) * $per_page, $per_page); + return [ - 'items' => $response['body']['items'], - 'total' => $response['body']['total_count'], + 'items' => $repositoriesInRequestedPage, + 'total' => \count($repositories), ]; } diff --git a/src/VCS/Adapter/Git/Gitea.php b/src/VCS/Adapter/Git/Gitea.php index 944b87b..fc85bf4 100644 --- a/src/VCS/Adapter/Git/Gitea.php +++ b/src/VCS/Adapter/Git/Gitea.php @@ -113,7 +113,7 @@ public function createOrganization(string $orgName): string // Stub methods to satisfy abstract class requirements // These will be implemented in follow-up PRs - public function searchRepositories(string $owner, int $page, int $per_page, string $search = ''): array + public function searchRepositories(string $installationId, string $owner, int $page, int $per_page, string $search = ''): array { throw new Exception("Not implemented yet"); } diff --git a/tests/VCS/Base.php b/tests/VCS/Base.php index aa97753..cb3f1db 100644 --- a/tests/VCS/Base.php +++ b/tests/VCS/Base.php @@ -51,7 +51,8 @@ public function testGetOwnerName(): void public function testSearchRepositories(): void { - ['items' => $repos, 'total' => $total] = $this->vcsAdapter->searchRepositories('test-kh', 1, 2); + $installationId = System::getEnv('TESTS_GITHUB_INSTALLATION_ID') ?? ''; + ['items' => $repos, 'total' => $total] = $this->vcsAdapter->searchRepositories($installationId, 'test-kh', 1, 2); $this->assertCount(2, $repos); $this->assertSame(6, $total); }