fix use clause
[pharext/pharext.org] / app / Github / API.php
1 <?php
2
3 namespace app\Github;
4
5 use app\Github\Storage;
6 use app\Github\Exception;
7 use app\Pharext;
8
9 use merry\Config;
10
11 use http\Client;
12 use http\QueryString;
13 use http\Url;
14
15 use Psr\Log\LoggerInterface;
16
17 class API
18 {
19 /**
20 * @var Client
21 */
22 private $client;
23
24 /**
25 * @var Storage
26 */
27 private $tokens;
28
29 /**
30 * @var Storage
31 */
32 private $cache;
33
34 /**
35 * @var merry\Config
36 */
37 private $config;
38
39 /**
40 * @var int
41 */
42 private $maxAge;
43
44 /**
45 * @var \Psr\Log\LoggerInterface;
46 */
47 private $logger;
48
49 /**
50 * Queued promises
51 * @var array
52 */
53 private $queue;
54
55 function __construct(Config $config, LoggerInterface $logger, Storage $tokens = null, Storage $cache = null) {
56 $this->logger = $logger;
57 $this->config = $config;
58 $this->client = new Client("curl", "github");
59 $this->client->configure($config->http->configure->toArray());
60 $this->client->attach(new ClientObserver($logger));
61 $this->tokens = $tokens ?: new Storage\Session;
62 $this->cache = $cache;
63 }
64
65 /**
66 * Set maximum acceptable age of cache items
67 * @param int $seconds
68 */
69 function setMaxAge($seconds) {
70 $this->maxAge = $seconds;
71 return $this;
72 }
73
74 function getMaxAge() {
75 return $this->maxAge;
76 }
77
78 function getLogger() {
79 return $this->logger;
80 }
81
82 function getConfig() {
83 return $this->config;
84 }
85
86 function getClient() {
87 return $this->client;
88 }
89
90 function getTokenStorage() {
91 return $this->tokens;
92 }
93
94 function getCacheStorage() {
95 return $this->cache;
96 }
97
98 function getCacheKey($ident, $page = null) {
99 return sprintf("%s:%s:%s", $this->getToken(), $ident, $page ?: 1);
100 }
101
102 function hasToken() {
103 if ($this->tokens->get("access_token", $token)) {
104 $access_token = $token->getValue();
105 if (isset($access_token)) {
106 return true;
107 }
108 $this->dropToken();
109 }
110 return false;
111 }
112
113 function setToken($token) {
114 $this->tokens->set("access_token", new Storage\Item(
115 $token,
116 $this->config->storage->token->ttl
117 ));
118 }
119
120 function getToken() {
121 if ($this->tokens->get("access_token", $token, true)) {
122 return $token->getValue();
123 }
124 if (isset($token)) {
125 $this->logger->notice("Token expired", $token);
126 throw new Exception\TokenExpired($token->getLTL());
127 }
128 throw new Exception\TokenNotSet;
129 }
130
131 function dropToken() {
132 $this->tokens->del("access_token");
133 }
134
135 function getAuthUrl($callback_url) {
136 $state = base64_encode(openssl_random_pseudo_bytes(24));
137 $this->tokens->set("state", new Storage\Item($state, 5*60));
138 $param = [
139 "state" => $state,
140 "client_id" => $this->config->client->id,
141 "scope" => $this->config->client->scope,
142 "redirect_uri" => (string) $callback_url,
143 ];
144 return new Url("https://github.com/login/oauth/authorize", [
145 "query" => new QueryString($param)
146 ], 0);
147 }
148
149 function fetchToken($code, $state) {
150 if (!$this->tokens->get("state", $orig_state, true)) {
151 if (isset($orig_state)) {
152 $this->logger->notice("State expired", compact("state", "orig_state"));
153 throw new Exception\StateExpired($orig_state->getLTL());
154 }
155 throw new Exception\StateNotSet;
156 }
157 if ($state !== $orig_state->getValue()) {
158 $this->logger->warning("State mismatch", compact("state", "orig_state"));
159 throw new Exception\StateMismatch($orig_state->getValue(), $state);
160 }
161
162 return $this->queue(new API\Users\ReadAuthToken($this, [
163 "code" => $code,
164 "client_id" => $this->config->client->id,
165 "client_secret" => $this->config->client->secret,
166 ]));
167 }
168
169 function queue(API\Call $call) {
170 return $this->queue[] = $call();
171 }
172
173 function drain() {
174 $queue = $this->queue;
175 $this->queue = array();
176 $this->client->send();
177 return $queue;
178 }
179
180 function readAuthUser() {
181 return $this->queue(new API\Users\ReadAuthUser($this));
182 }
183
184 function listRepos($page) {
185 return $this->queue(new API\Repos\ListRepos($this, compact("page")));
186 }
187
188 function readRepo($repo) {
189 return $this->queue(new API\Repos\ReadRepo($this, compact("repo")));
190 }
191
192 /**
193 * Check if the pharext webhook is set and return it
194 * @param array $hooks
195 * @return stdClass hook
196 */
197 function checkHook($hooks) {
198 if (!empty($hooks)) {
199 foreach ($hooks as $hook) {
200 if ($hook->name === "web" && $hook->config->url === $this->config->hook->url) {
201 return $hook;
202 }
203 }
204 }
205 return null;
206 }
207
208 /**
209 * Check if the pharext webhook is set for the repo and return it
210 * @param object $repo
211 * @return stdClass hook
212 */
213 function checkRepoHook($repo) {
214 if (!empty($repo->hooks)) {
215 return $this->checkHook($repo->hooks);
216 }
217 return null;
218 }
219
220 function listHooks($repo) {
221 return $this->queue(new API\Hooks\ListHooks($this, compact("repo")));
222 }
223
224 function listReleases($repo, $page) {
225 return $this->queue(new API\Releases\ListReleases($this, compact("repo", "page")));
226 }
227
228 function listTags($repo, $page) {
229 return $this->queue(new API\Tags\ListTags($this, compact("repo", "page")));
230 }
231
232 function readContents($repo, $path = null) {
233 return $this->queue(new API\Repos\ReadContents($this, compact("repo", "path")));
234 }
235
236 function createRepoHook($repo, $conf) {
237 return $this->queue(new API\Hooks\CreateHook($this, compact("repo", "conf")));
238 }
239
240 function updateRepoHook($repo, $id, $conf) {
241 return $this->queue(new API\Hooks\UpdateHook($this, compact("repo", "id", "conf")));
242 }
243
244 function deleteRepoHook($repo, $id) {
245 return $this->queue(new API\Hooks\DeleteHook($this, compact("repo", "id")));
246 }
247
248 function createRelease($repo, $tag) {
249 return $this->queue(new API\Releases\CreateRelease($this, compact("repo", "tag")));
250 }
251
252 function publishRelease($repo, $id, $tag) {
253 return $this->queue(new API\Releases\PublishRelease($this, compact("repo", "id", "tag")));
254 }
255
256 function createReleaseAsset($url, $asset, $type) {
257 return $this->queue(new API\Releases\CreateReleaseAsset($this, compact("url", "asset", "type")));
258 }
259
260 function listReleaseAssets($repo, $id) {
261 return $this->queue(new API\Releases\ListReleaseAssets($this, compact("repo", "id")));
262 }
263
264 function uploadAssetForRelease($repo, $release, $config = null) {
265 return $this->listHooks($repo->full_name)->then(function($result) use($release, $repo, $config) {
266 list($repo->hooks) = $result;
267 $phar = new Pharext\Package(
268 $repo->clone_url,
269 $release->tag_name,
270 $repo->name,
271 $config ?: (array) $this->checkRepoHook($repo)->config
272 );
273 $name = $phar->build();
274 $url = uri_template($release->upload_url, compact("name"));
275 $promise = $this->createReleaseAsset($url, $phar, "application/phar");
276 if ($release->draft) {
277 return $promise->then(function($result) use($release, $repo) {
278 return $this->publishRelease($repo->full_name, $release->id, $release->tag_name);
279 });
280 }
281 return $promise;
282 });
283 }
284
285 function createReleaseFromTag($repo, $tag_name, $config = null) {
286 return $this->createRelease($repo->full_name, $tag_name)->then(function($result) use($repo, $config) {
287 list($release) = $result;
288 return $this->uploadAssetForRelease($repo, $release, $config);
289 });
290 }
291
292 }