Repository navigation
Expand file tree
/
Copy pathGithub.php
More file actions
262 lines (223 loc) · 7.7 KB
/
Copy pathGithub.php
File metadata and controls
262 lines (223 loc) · 7.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
<?php
namespace JordanPartridge\GithubClient;
use JordanPartridge\GithubClient\Connectors\GithubConnector;
use JordanPartridge\GithubClient\Data\RateLimitDTO;
use JordanPartridge\GithubClient\Data\Repos\RepoData;
use JordanPartridge\GithubClient\Exceptions\ApiException;
use JordanPartridge\GithubClient\Exceptions\NetworkException;
use JordanPartridge\GithubClient\Requests\RateLimit\Get;
use JordanPartridge\GithubClient\Auth\GitHubAppAuthentication;
use JordanPartridge\GithubClient\Resources\ActionsResource;
use JordanPartridge\GithubClient\Resources\CommentsResource;
use JordanPartridge\GithubClient\Resources\CommitResource;
use JordanPartridge\GithubClient\Resources\FileResource;
use JordanPartridge\GithubClient\Resources\InstallationsResource;
use JordanPartridge\GithubClient\Resources\IssuesResource;
use JordanPartridge\GithubClient\Resources\PullRequestResource;
use JordanPartridge\GithubClient\Resources\ReleasesResource;
use JordanPartridge\GithubClient\Resources\RepoResource;
use JordanPartridge\GithubClient\ValueObjects\Repo;
use Saloon\Http\Response;
class Github
{
use Concerns\ValidatesRepoName;
public function __construct(
protected GithubConnector $connector,
) {}
public function connector(): GithubConnector
{
return $this->connector;
}
public function repos(): RepoResource
{
return new RepoResource($this);
}
public function commits(): CommitResource
{
return new CommitResource($this);
}
public function files(): FileResource
{
return new FileResource($this);
}
public function pullRequests(): PullRequestResource
{
return new PullRequestResource($this);
}
public function actions(): ActionsResource
{
return new ActionsResource($this);
}
public function issues(): IssuesResource
{
return new IssuesResource($this);
}
public function comments(): CommentsResource
{
return new CommentsResource($this);
}
public function releases(): ReleasesResource
{
return new ReleasesResource($this);
}
public function installations(): InstallationsResource
{
return new InstallationsResource($this);
}
/**
* Get the current rate limit status for all resources.
*
* @return array<string, RateLimitDTO> Array of rate limit DTOs keyed by resource type
*
* @throws ApiException When the API request fails
* @throws NetworkException When network connectivity issues occur
*/
public function getRateLimitStatus(): array
{
try {
$request = new Get();
$response = $this->connector->send($request);
if (! $response->successful()) {
throw new ApiException($response);
}
return $request->createDtoFromResponse($response);
} catch (\Exception $e) {
if ($e instanceof ApiException) {
throw $e;
}
throw new NetworkException('rate limit check', $e->getMessage(), previous: $e);
}
}
/**
* Get rate limit status for a specific resource type.
*
* @param string $resource The resource type (core, search, graphql, etc.)
*
* @return RateLimitDTO The rate limit information for the specified resource
*
* @throws ApiException When the API request fails or resource not found
*/
public function getRateLimitForResource(string $resource = 'core'): RateLimitDTO
{
$rateLimits = $this->getRateLimitStatus();
if (! isset($rateLimits[$resource])) {
throw new ApiException(
response: $this->connector->send(new Get()),
message: "Rate limit resource '{$resource}' not found",
);
}
return $rateLimits[$resource];
}
/**
* Check if any rate limits are exceeded.
*/
public function hasRateLimitExceeded(): bool
{
try {
$rateLimits = $this->getRateLimitStatus();
foreach ($rateLimits as $rateLimit) {
if ($rateLimit->isExceeded()) {
return true;
}
}
return false;
} catch (\Exception) {
// If we can't check rate limits, assume we haven't exceeded them
return false;
}
}
/**
* Get a repository by full name with automatic validation.
*
* @param string $fullName The full repository name (owner/repo)
*
* @return RepoData The repository data
*/
public function getRepo(string $fullName): RepoData
{
$repo = Repo::fromFullName($fullName); // Validates the name
return $this->repos()->get($repo);
}
/**
* Delete a repository by full name with automatic validation.
*
* @param string $fullName The full repository name (owner/repo)
*
* @return Response The deletion response
*/
public function deleteRepo(string $fullName): Response
{
$repo = Repo::fromFullName($fullName); // Ensures validation
return $this->repos()->delete($repo);
}
/**
* Create a new GitHub client authenticated as a GitHub App installation.
*
* This creates a new instance configured to act on behalf of a specific
* installation, using installation tokens instead of JWT tokens.
*
* @param int $installationId The installation ID to authenticate as
*
* @return self A new Github instance authenticated for the installation
*/
public static function forInstallation(int $installationId): self
{
// Get GitHub App config
$appId = config('github-client.github_app.app_id');
$privateKey = self::resolvePrivateKey();
if (! $appId || ! $privateKey) {
throw new \RuntimeException(
'GitHub App not configured. Set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY or GITHUB_APP_PRIVATE_KEY_PATH',
);
}
$auth = new GitHubAppAuthentication(
appId: $appId,
privateKey: $privateKey,
installationId: (string) $installationId,
);
$connector = new GithubConnector($auth);
return new self($connector);
}
/**
* Create a new GitHub client with custom GitHub App credentials.
*
* This allows using GitHub App authentication without relying on config files.
*
* @param string $appId The GitHub App ID
* @param string $privateKey The private key (PEM format or base64 encoded)
* @param int|null $installationId Optional installation ID
*
* @return self A new Github instance with GitHub App authentication
*/
public static function withApp(string $appId, string $privateKey, ?int $installationId = null): self
{
$auth = new GitHubAppAuthentication(
appId: $appId,
privateKey: $privateKey,
installationId: $installationId ? (string) $installationId : null,
);
$connector = new GithubConnector($auth);
return new self($connector);
}
/**
* Resolve the private key from config.
*/
private static function resolvePrivateKey(): ?string
{
// Try direct key first
$key = config('github-client.github_app.private_key');
if ($key) {
return $key;
}
// Try file path
$path = config('github-client.github_app.private_key_path');
if ($path && file_exists($path)) {
return file_get_contents($path);
}
// Try base_path for relative paths
if ($path && file_exists(base_path($path))) {
return file_get_contents(base_path($path));
}
return null;
}
}