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