refactor guthub api
authorMichael Wallner <mike@php.net>
Mon, 11 May 2015 08:47:48 +0000 (10:47 +0200)
committerMichael Wallner <mike@php.net>
Mon, 11 May 2015 08:47:48 +0000 (10:47 +0200)
50 files changed:
app/Controller/Github/Callback.php
app/Controller/Github/Index.php
app/Controller/Github/Repo.php
app/Github/API.php
app/Github/API/Call.php [new file with mode: 0644]
app/Github/API/Hooks/CreateHook.php [new file with mode: 0644]
app/Github/API/Hooks/DeleteHook.php [new file with mode: 0644]
app/Github/API/Hooks/ListHooks.php [new file with mode: 0644]
app/Github/API/Releases/CreateRelease.php [new file with mode: 0644]
app/Github/API/Releases/CreateReleaseAsset.php [new file with mode: 0644]
app/Github/API/Releases/ListReleaseAssets.php [new file with mode: 0644]
app/Github/API/Releases/ListReleases.php [new file with mode: 0644]
app/Github/API/Repos/ListRepos.php [new file with mode: 0644]
app/Github/API/Repos/ReadContents.php [new file with mode: 0644]
app/Github/API/Repos/ReadRepo.php [new file with mode: 0644]
app/Github/API/Tags/ListTags.php [new file with mode: 0644]
app/Github/API/Users/ReadAuthToken.php [new file with mode: 0644]
app/Github/API/Users/ReadAuthUser.php [new file with mode: 0644]
app/Github/Create.php [deleted file]
app/Github/Create/Release.php [deleted file]
app/Github/Create/ReleaseAsset.php [deleted file]
app/Github/Create/Webhook.php [deleted file]
app/Github/Delete.php [deleted file]
app/Github/Delete/Webhook.php [deleted file]
app/Github/Exception/ContentsFetchFailed.php [deleted file]
app/Github/Exception/HooksFetchFailed.php [deleted file]
app/Github/Exception/ReleaseAssetCreateFailed.php [deleted file]
app/Github/Exception/ReleaseCreateFailed.php [deleted file]
app/Github/Exception/ReleasesFetchFailed.php [deleted file]
app/Github/Exception/ReposFetchFailed.php [deleted file]
app/Github/Exception/RequestException.php
app/Github/Exception/TokenFetchFailed.php [deleted file]
app/Github/Exception/UserFetchFailed.php [deleted file]
app/Github/Exception/WebhookCreateFailed.php [deleted file]
app/Github/Exception/WebhookDeleteFailed.php [deleted file]
app/Github/Fetch.php [deleted file]
app/Github/Fetch/Contents.php [deleted file]
app/Github/Fetch/Hooks.php [deleted file]
app/Github/Fetch/Releases.php [deleted file]
app/Github/Fetch/Repo.php [deleted file]
app/Github/Fetch/Repos.php [deleted file]
app/Github/Fetch/Tags.php [deleted file]
app/Github/Fetch/Token.php [deleted file]
app/Github/Fetch/User.php [deleted file]
app/Github/Links.php [new file with mode: 0644]
app/views/alert.phtml
app/views/github/callback.phtml
app/views/github/index.phtml
app/views/github/repo.phtml
config/app.ini

index d6d11920c6bd14ab3ecd465356536b1b49e94175..496baf331f9973def275201755e1ad658ca51c3c 100644 (file)
@@ -28,24 +28,20 @@ class Callback extends Github
                                "error" => $this->app->getRequest()->getQuery("error_description")
                        ]);
                } else {
-                       try {
-                               $this->github->fetchToken(
-                                       $this->app->getRequest()->getQuery("code"),
-                                       $this->app->getRequest()->getQuery("state"),
-                                       function($token) {
-                                               $this->github->setToken($token->access_token);
-                                               $this->github->fetchUser($this->createUserCallback($token));
-                               })->send();
-                               if (isset($this->session->returnto)) {
-                                       $returnto = $this->session->returnto;
-                                       unset($this->session->returnto);
-                                       $this->app->redirect($returnto);
-                               } else {
-                                       $this->app->redirect(
-                                               $this->app->getBaseUrl()->mod("./github"));
-                               }
-                       } catch (Exception $exception) {
-                               $this->app->getView()->addData(compact("exception"));
+                       $this->github->fetchToken(
+                               $this->app->getRequest()->getQuery("code"),
+                               $this->app->getRequest()->getQuery("state"),
+                               function($token) {
+                                       $this->github->setToken($token->access_token);
+                                       $this->github->fetchUser($this->createUserCallback($token));
+                       })->send();
+                       if (isset($this->session->returnto)) {
+                               $returnto = $this->session->returnto;
+                               unset($this->session->returnto);
+                               $this->app->redirect($returnto);
+                       } else {
+                               $this->app->redirect(
+                                       $this->app->getBaseUrl()->mod("./github"));
                        }
                }
                $this->app->display("github/callback");
index f55eb0c2e4cdece8ebbc739199bd5f83705a2937..935f249e3fa6e0238f8881c2d31d2bae694622be 100644 (file)
@@ -4,27 +4,20 @@ namespace app\Controller\Github;
 
 use app\Controller\Github;
 
-use http\QueryString;
-
 class Index extends Github
 {
        function __invoke(array $args = null) {
                if ($this->checkToken()) {
-                       try {
-                               $this->github->fetchRepos(
-                                       $this->app->getRequest()->getQuery("page"), 
-                                       [$this, "reposCallback"]
-                               )->send();
-                       } catch (\app\Github\Exception $exception) {
-                               $this->view->addData(compact("exception"));
-                       }
+                       $this->github->fetchRepos(
+                               $this->app->getRequest()->getQuery("page"), 
+                               [$this, "reposCallback"]
+                       )->send();
                        $this->app->display("github/index");
                }
        }
 
        function reposCallback($repos, $links) {
-               $this->app->getView()->addData(compact("repos"));
-               $this->app->getView()->registerFunction("link", $this->createLinkGenerator($links));
+               $this->app->getView()->addData(compact("repos", "links"));
 
                foreach ($repos as $repo) {
                        $this->github->fetchHooks($repo->full_name, function($hooks) use($repo) {
index f194d8e726b91ac52294e1651a542b95d0d8ea4e..8caba2af43ef2f7412a08b15a40be068451feabb 100644 (file)
@@ -8,15 +8,12 @@ class Repo extends Github
 {
        function __invoke(array $args = null) {
                extract($args);
+               $this->app->getView()->addData(compact("owner", "name"));
                if ($this->checkToken()) {
-                       try {
-                               $this->github->fetchRepo(
-                                       "$owner/$name",
-                                       [$this, "repoCallback"]
-                               )->send();
-                       } catch (\app\Github\Exception $exception) {
-                               $this->app->getView()->addData(compact("exception", "owner", "name"));
-                       }
+                       $this->github->fetchRepo(
+                               "$owner/$name",
+                               [$this, "repoCallback"]
+                       )->send();
                        
                        if (($modal = $this->app->getRequest()->getQuery("modal"))) {
                                $this->app->getView()->addData(compact("modal") + [
@@ -28,7 +25,7 @@ class Repo extends Github
                }
        }
 
-       function repoCallback($repo, $links) {
+       function repoCallback($repo) {
                $this->app->getView()->addData(compact("repo") + [
                        "title" => "Github: {$repo->name}"
                ]);
index 8737c16ce5a9edeccf692baed7ec8ad04068d3f4..10237693420868ee733805950d8f8e77c5e425ab 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace app\Github;
 
+use app\Github\API;
 use app\Github\Storage;
 use app\Github\Exception;
 
@@ -107,71 +108,72 @@ class API
                if ($state !== $orig_state) {
                        throw new Exception\StateMismatch($orig_state, $state);
                }
-
-               $fetch = new Fetch\Token($this, compact("code") + $this->config->client->toArray());
-               return $fetch($callback);
+               
+               $call = new API\Users\ReadAuthToken($this, [
+                       "code" => $code,
+                       "client_id" => $this->config->client->id,
+                       "client_secret" => $this->config->client->secret,
+               ]);
+               return $call($callback);
        }
        
        function fetchUser(callable $callback) {
-               $fetch = new Fetch\User($this);
-               return $fetch($callback);
+               $call = new API\Users\ReadAuthUser($this);
+               return $call($callback);
        }
 
        function fetchRepos($page, callable $callback) {
-               $fetch = new Fetch\Repos($this);
-               $fetch->setPage($page);
-               return $fetch($callback);
+               $call = new API\Repos\ListRepos($this, compact("page"));
+               return $call($callback);
        }
 
        function fetchRepo($repo, callable $callback) {
-               $fetch = new Fetch\Repo($this, compact("repo"));
-               return $fetch($callback);
+               $call = new API\Repos\ReadRepo($this, compact("repo"));
+               return $call($callback);
        }
 
        function fetchHooks($repo, callable $callback) {
-               $fetch = new Fetch\Hooks($this, compact("repo"));
-               return $fetch($callback);
+               $call = new API\Hooks\ListHooks($this, compact("repo"));
+               return $call($callback);
        }
 
        function fetchReleases($repo, $page, callable $callback) {
-               $fetch = new Fetch\Releases($this, compact("repo"));
-               $fetch->setPage($page);
-               return $fetch($callback);
+               $call = new API\Releases\ListReleases($this, compact("repo", "page"));
+               return $call($callback);
        }
 
        function fetchTags($repo, $page, callable $callback) {
-               $fetch = new Fetch\Tags($this, compact("repo"));
-               $fetch->setPage($page);
-               return $fetch($callback);
+               $call = new API\Tags\ListTags($this, compact("repo", "page"));
+               return $call($callback);
        }
        
        function fetchContents($repo, $path, callable $callback) {
-               $fetch = new Fetch\Contents($this, compact("repo", "path"));
-               return $fetch($callback);
+               $call = new API\Repos\ReadContents($this, compact("repo", "path"));
+               return $call($callback);
        }
        
        function createRepoHook($repo, $conf, callable $callback) {
-               $create = new Create\Webhook($this, compact("repo", "conf"));
-               return $create($callback);
+               $call = new API\Hooks\CreateHook($this, compact("repo", "conf"));
+               return $call($callback);
        }
        
        function updateRepoHook($repo, $id, $conf, callable $callback) {
-               $update = new Update\Webhook($this, compact("repo", "id", "conf"));
-               return $update($callback);
+               $call = new API\Hooks\UpdateHook($this, compact("repo", "id", "conf"));
+               return $call($callback);
        }
        
        function deleteRepoHook($repo, $id, callable $callback) {
-               $delete = new Delete\Webhook($this, compact("repo", "id"));
-               return $delete($callback);
+               $call = new API\Hooks\DeleteHook($this, compact("repo", "id"));
+               return $call($callback);
        }
        
        function createRelease($repo, $tag, callable $callback) {
-               $create = new Create\Release($this, compact("repo", "tag"));
-               return $create($callback);
+               $call = new API\Releases\CreateRelease($this, compact("repo", "tag"));
+               return $call($callback);
        }
        
        function createReleaseAsset($url, $asset, $type, callable $callback) {
-               $create = new Create\ReleaseAsset($this, compact("url", "asset", "type"));
-               return $create($callback);
+               $call = new API\Releases\CreateReleaseAsset($this, compact("url", "asset", "type"));
+               return $call($callback);
        }
 }
diff --git a/app/Github/API/Call.php b/app/Github/API/Call.php
new file mode 100644 (file)
index 0000000..f40e486
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+
+namespace app\Github\API;
+
+use app\Github\API;
+use http\QueryString;
+use http\Url;
+use merry\Config;
+
+abstract class Call
+{
+       /**
+        * @var Config
+        */
+       protected $config;
+
+       /**
+        * @var \app\Gituhub\API
+        */
+       protected $api;
+       
+       /**
+        * @var array
+        */
+       protected $args = [];
+       
+       /**
+        * @var \http\Url
+        */
+       protected $url;
+       
+       /**
+        * @var QueryString
+        */
+       protected $query;
+       
+       /**
+        * Queue this call to the API client
+        */
+       abstract function enqueue(callable $callback);
+       
+       /**
+        * @param API $api
+        * @param array $args
+        */
+       function __construct(API $api, array $args = null) {
+               $this->api = $api;
+               $this->config = $this->api->getConfig();
+               $this->url = new Url($this->config->api->url, null, 0);
+               
+               if ($args) {
+                       $this->args = $args;
+               }
+               if (isset($this->config->api->call->{$this}->args)) {
+                       $this->args += $this->config->api->call->{$this}->args->toArray();
+               }
+       }
+       
+       function __invoke(callable $callback) {
+               if (empty($this->args["fresh"]) && ($cache = $this->api->getCacheStorage())) {
+                       $key = $this->getCacheKey();
+                       
+                       if ($cache->get($key, $cached)) {
+                               call_user_func_array($callback, $cached);
+                               return $this->api->getClient();
+                       }
+               }
+               
+               $this->enqueue($callback);
+               return $this->api->getClient();
+       }
+       
+       /**
+        * Get type of call
+        * @return string
+        */
+       function __toString() {
+               $parts = explode("\\", get_class($this));
+               return strtolower(end($parts));
+       }
+       
+       /**
+        * Get associated cache storage
+        * @param int $ttl out param of configure ttl
+        * @return Storage
+        */
+       function getCache(&$ttl = null) {
+               if (isset($this->config->storage->cache->{$this}->ttl)) {
+                       $ttl = $this->config->storage->cache->{$this}->ttl;
+               }
+               return $this->api->getCacheStorage();
+       }
+       
+       function getCacheKey() {
+               return sprintf("github:%s:%s:%s", $this->api->getToken(), $this, 
+                       new QueryString($this->args));
+       }
+
+       function readFromCache(&$cached = null, &$ttl = null) {
+               if (empty($this->args["fresh"]) && ($cache = $this->api->getCacheStorage())) {
+                       $key = $this->getCacheKey();
+                       return $cache->get($key, $cached, $ttl);
+               }
+               return false;
+       }
+       
+       function saveToCache($fresh) {
+               if (($cache = $this->api->getCacheStorage())) {
+                       if (isset($this->config->storage->cache->{$this}->ttl)) {
+                               $ttl = $this->config->storage->cache->{$this}->ttl;
+                       } else {
+                               $ttl = 0;
+                       }
+                       
+                       $key = $this->getCacheKey();
+                       $cache->set($key, $fresh, $ttl);
+               }
+       }
+}
diff --git a/app/Github/API/Hooks/CreateHook.php b/app/Github/API/Hooks/CreateHook.php
new file mode 100644 (file)
index 0000000..325dca7
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+namespace app\Github\API\Hooks;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+
+class CreateHook extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod("./repos/". $this->args["repo"] ."/hooks");
+               $request = new Request("POST", $url, [
+                       "Authorization" => "token " . $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+                       "Content-Type" => "application/json",
+               ]);
+               
+               if (!empty($this->args["conf"]["tag"])) {
+                       $events[] = "create";
+               }
+               if (!empty($this->args["conf"]["release"])) {
+                       $events[] = "release";
+               }
+               
+               $request->getBody()->append(json_encode([
+                       "name" => "web",
+                       "events" => $events,
+                       "config" => [
+                               "url" => $this->config->hook->url,
+                               "content_type" => $this->config->hook->content_type,
+                               "insecure_ssl" => $this->config->hook->insecure_ssl,
+                               "secret" => $this->config->client->secret, // FIXME: bad idea?
+                       ]
+               ]));
+               
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getReesponseCode() != 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Hooks/DeleteHook.php b/app/Github/API/Hooks/DeleteHook.php
new file mode 100644 (file)
index 0000000..311e64d
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+namespace app\Github\API\Hooks;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+
+class DeleteHook extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./repos/{+repo}/hooks{/id}", $this->args));
+               $request = new Request("DELETE", $url, [
+                       "Authorization" => "token " . $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400) {
+                               throw new RequestException($response);
+                       }
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Hooks/ListHooks.php b/app/Github/API/Hooks/ListHooks.php
new file mode 100644 (file)
index 0000000..6cd784e
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace app\Github\API\Hooks;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use app\Github\Links;
+use http\Client\Request;
+
+class ListHooks extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./repos/{+repo}/hooks{?page,per_page}", $this->args));
+               $request = new Request("GET", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $links = new Links($response->getHeader("Link"));
+                       $this->saveToCache([$json, $links]);
+                       $callback($json, $links);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Releases/CreateRelease.php b/app/Github/API/Releases/CreateRelease.php
new file mode 100644 (file)
index 0000000..21f849f
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace app\Github\API\Releases;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+
+class CreateRelease extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod("/repos/". $this->args["repo"] ."/releases");
+               $request = new Request("POST", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+                       "Content-Type" => "application/json",
+               ]);
+               $request->getBody()->append(json_encode([
+                       "tag_name" => $this->args["tag"]
+               ]));
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Releases/CreateReleaseAsset.php b/app/Github/API/Releases/CreateReleaseAsset.php
new file mode 100644 (file)
index 0000000..c69d805
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+namespace app\Github\API\Releases;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+use http\Message\Body;
+
+class CreateReleaseAsset extends Call
+{
+       function enqueue(callable $callback) {
+               $body = new Body(fopen($this->args["asset"], "rb"));
+               $request = new Request("POST", $this->args["url"], [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+                       "Content-Type" => $this->args["type"],
+               ], $body);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Releases/ListReleaseAssets.php b/app/Github/API/Releases/ListReleaseAssets.php
new file mode 100644 (file)
index 0000000..b365183
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace app\Github\API\Releases;
+
+class ListReleaseAssets extends \app\Github\API\Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./repos/{+repo}/releases{/release}/assets", $this->args));
+               $request = new \http\Client\Request("GET", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new \app\Github\Exception\RequestException($response);
+                       }
+                       $links = new Links($response->getHeader("Link"));
+                       $this->saveToCache([$json, $links]);
+                       $callback($json, $links);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Releases/ListReleases.php b/app/Github/API/Releases/ListReleases.php
new file mode 100644 (file)
index 0000000..c1760b9
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace app\Github\API\Releases;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use app\Github\Links;
+use http\Client\Request;
+
+class ListReleases extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./repos/{+repo}/releases{?page,per_page}", $this->args));
+               $request = new Request("GET", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $links = new Links($response->getHeader("Link"));
+                       $this->saveToCache([$json, $links]);
+                       $callback($json, $links);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Repos/ListRepos.php b/app/Github/API/Repos/ListRepos.php
new file mode 100644 (file)
index 0000000..c842196
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace app\Github\API\Repos;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use app\Github\Links;
+use http\Client\Request;
+
+class ListRepos extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./user/repos{?page,per_page}", $this->args));
+               $request = new Request("GET", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $links = new Links($response->getHeader("Link"));
+                       $this->saveToCache([$json, $links]);
+                       $callback($json, $links);
+                       return true;
+               });
+       }
+}
\ No newline at end of file
diff --git a/app/Github/API/Repos/ReadContents.php b/app/Github/API/Repos/ReadContents.php
new file mode 100644 (file)
index 0000000..02b28fb
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace app\Github\API\Repos;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+
+class ReadContents extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./repos/{+repo}/contents/{+path}", $this->args));
+               $request = new Request("GET", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $this->saveToCache($json);
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Repos/ReadRepo.php b/app/Github/API/Repos/ReadRepo.php
new file mode 100644 (file)
index 0000000..e432d2a
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace app\Github\API\Repos;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+
+class ReadRepo extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./repos/{+repo}", $this->args));
+               $request = new Request("GET", $url, [
+                       "Authorization" => "token " . $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $this->saveToCache($json);
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Tags/ListTags.php b/app/Github/API/Tags/ListTags.php
new file mode 100644 (file)
index 0000000..b7a1d4c
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace app\Github\API\Tags;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use app\Github\Links;
+use http\Client\Request;
+
+class ListTags extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod(uri_template("./repos/{+repo}/tags{?page,per_page}", $this->args));
+               $request = new Request("GET", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $links = new Links($response->getHeader("Link"));
+                       $this->saveToCache([$json, $links]);
+                       $callback($json, $links);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Users/ReadAuthToken.php b/app/Github/API/Users/ReadAuthToken.php
new file mode 100644 (file)
index 0000000..4430b45
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+namespace app\Github\API\Users;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+use http\QueryString;
+
+class ReadAuthToken extends Call
+{
+       function enqueue(callable $callback) {
+               $request = new Request("POST", "https://github.com/login/oauth/access_token", [
+                       "Accept" => "application/json",
+               ]);
+               $request->getBody()->append(new QueryString($this->args));
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/API/Users/ReadAuthUser.php b/app/Github/API/Users/ReadAuthUser.php
new file mode 100644 (file)
index 0000000..5feb7c8
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace app\Github\API\Users;
+
+use app\Github\API\Call;
+use app\Github\Exception\RequestException;
+use http\Client\Request;
+
+class ReadAuthUser extends Call
+{
+       function enqueue(callable $callback) {
+               $url = $this->url->mod("./user");
+               $request = new Request("GET", $url, [
+                       "Authorization" => "token ". $this->api->getToken(),
+                       "Accept" => $this->config->api->accept,
+               ]);
+               $this->api->getClient()->enqueue($request, function($response) use($callback) {
+                       if ($response->getResponseCode() >= 400 || null === ($json = json_decode($response->getBody()))) {
+                               throw new RequestException($response);
+                       }
+                       $this->saveToCache($json);
+                       $callback($json);
+                       return true;
+               });
+       }
+}
diff --git a/app/Github/Create.php b/app/Github/Create.php
deleted file mode 100644 (file)
index 0f3f31a..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-namespace app\Github;
-
-use http\Header;
-use http\Url;
-
-abstract class Create
-{
-       /**
-        * @var \app\Github\API
-        */
-       protected $api;
-
-       /**
-        * @var \merry\Config
-        */
-       protected $config;
-
-       /**
-        * @var array
-        */
-       protected $args;
-
-       /**
-        * @var \http\Url
-        */
-       protected $url;
-
-       function __construct(API $api, array $args = []) {
-               $this->api = $api;
-               $this->config = $api->getConfig();
-               $this->args = $args;
-               $this->url = new Url("https://api.github.com/", null, 0);
-       }
-
-       function __toString() {
-               $parts = explode("\\", get_class($this));
-               return strtolower(end($parts));
-       }
-       
-       abstract function getRequest();
-       abstract function getException($message, $code, $previous = null);
-
-       function __invoke(callable $callback) {
-               $this->enqueue($callback);
-               return $this->api->getClient();
-       }
-
-       protected function wrap(callable $callback) {
-               return function($response) use($callback) {
-                       $rc = $response->getResponseCode();
-
-                       if ($rc !== 201) {
-                               if ($response->getHeader("Content-Type", Header::class)->match("application/json", Header::MATCH_WORD)) {
-                                       $message = json_decode($response->getBody())->message;
-                               } else {
-                                       $message = $response->getBody();
-                               }
-                               throw $this->getException($message, $rc);
-                       }
-
-                       $json = json_decode($response->getBody());
-
-                       if (isset($json->error)) {
-                               throw $this->getException($json->error_description, $rc);
-                       }
-
-                       $callback($json);
-
-                       return true;
-               };
-       }
-
-       function enqueue(callable $callback) {
-               $request = $this->getRequest();
-               $wrapper = $this->wrap($callback);
-               return $this->api->getClient()->enqueue($request, $wrapper);
-       }
-}
\ No newline at end of file
diff --git a/app/Github/Create/Release.php b/app/Github/Create/Release.php
deleted file mode 100644 (file)
index 3f9f710..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-namespace app\Github\Create;
-
-use app\Github\Create;
-use app\Github\Exception\ReleaseCreateFailed;
-use http\Client\Request;
-
-class Release extends Create
-{
-       function getRequest() {
-               $url = $this->url->mod("/repos/". $this->args["repo"] ."/releases");
-               $request = new Request("POST", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Content-Type" => "application/json",
-                       "Authorization" => "token ". $this->api->getToken()
-               ]);
-               $request->getBody()->append(json_encode([
-                       "tag_name" => $this->args["tag"]
-               ]));
-               return $request;
-       }
-       
-       function getException($message, $code, $previous = null) {
-               return new ReleaseCreateFailed($message, $code, $previous);
-       }
-}
diff --git a/app/Github/Create/ReleaseAsset.php b/app/Github/Create/ReleaseAsset.php
deleted file mode 100644 (file)
index 538273d..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace app\Github\Create;
-
-use app\Github\Create;
-use app\Github\Exception\ReleaseAssetCreateFailed;
-use http\Client\Request;
-
-class ReleaseAsset extends Create
-{
-       function getRequest() {
-               $body = new \http\Message\Body(fopen($this->args["asset"], "rb"));
-               $request = new Request("POST", $this->args["url"], [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Content-Type" => $this->args["type"],
-                       "Authorization" => "token ". $this->api->getToken()
-               ], $body);
-               return $request;
-       }
-       
-       function getException($message, $code, $previous = null) {
-               return new ReleaseAssetCreateFailed($message, $code, $previous);
-       }
-}
diff --git a/app/Github/Create/Webhook.php b/app/Github/Create/Webhook.php
deleted file mode 100644 (file)
index 72ff2d7..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-namespace app\Github\Create;
-
-use app\Github\Create;
-use app\Github\Exception\WebhookCreateFailed;
-use http\Client\Request;
-
-class Webhook extends Create
-{
-       function getRequest() {
-               $url = $this->url->mod("./repos/". $this->args["repo"] ."/hooks");
-               $request = new Request("POST", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Content-Type" => "application/json",
-                       "Authorization" => "token " . $this->api->getToken(),
-               ]);
-               
-               if (!empty($this->args["conf"]["tag"])) {
-                       $events[] = "create";
-               }
-               if (!empty($this->args["conf"]["release"])) {
-                       $events[] = "release";
-               }
-               
-               $request->getBody()->append(json_encode([
-                       "name" => "web",
-                       "events" => $events,
-                       "config" => [
-                               "url" => $this->config->hook->url,
-                               "content_type" => $this->config->hook->content_type,
-                               "insecure_ssl" => $this->config->hook->insecure_ssl,
-                               "secret" => $this->config->client->secret, // FIXME: bad idea?
-                       ]
-               ]));
-               return $request;
-       }
-       
-       function getException($message, $code, $previous = null) {
-               return new WebhookCreateFailed($message, $code, $previous);
-       }
-}
diff --git a/app/Github/Delete.php b/app/Github/Delete.php
deleted file mode 100644 (file)
index 4e859a6..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-
-namespace app\Github;
-
-use http\Header;
-use http\Url;
-
-abstract class Delete
-{
-       /**
-        * @var \app\Github\API
-        */
-       protected $api;
-
-       /**
-        * @var \merry\Config
-        */
-       protected $config;
-
-       /**
-        * @var array
-        */
-       protected $args;
-
-       /**
-        * @var \http\Url
-        */
-       protected $url;
-
-       function __construct(API $api, array $args = []) {
-               $this->api = $api;
-               $this->config = $api->getConfig();
-               $this->args = $args;
-               $this->url = new Url("https://api.github.com/", null, 0);
-       }
-
-       function __toString() {
-               $parts = explode("\\", get_class($this));
-               return strtolower(end($parts));
-       }
-       
-       abstract function getRequest();
-       abstract function getException($message, $code, $previous = null);
-
-       function __invoke(callable $callback) {
-               $this->enqueue($callback);
-               return $this->api->getClient();
-       }
-
-       protected function wrap(callable $callback) {
-               return function($response) use($callback) {
-                       $rc = $response->getResponseCode();
-
-                       if ($rc !== 204) {
-                               if ($response->getHeader("Content-Type", Header::class)->match("application/json", Header::MATCH_WORD)) {
-                                       $message = json_decode($response->getBody())->message;
-                               } else {
-                                       $message = $response->getBody();
-                               }
-                               throw $this->getException($message, $rc);
-                       }
-                       
-                       $callback();
-
-                       return true;
-               };
-       }
-
-       function enqueue(callable $callback) {
-               $request = $this->getRequest();
-               $wrapper = $this->wrap($callback);
-               return $this->api->getClient()->enqueue($request, $wrapper);
-       }
-}
\ No newline at end of file
diff --git a/app/Github/Delete/Webhook.php b/app/Github/Delete/Webhook.php
deleted file mode 100644 (file)
index 5493872..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace app\Github\Delete;
-
-use app\Github\Delete;
-use app\Github\Exception\WebhookDeleteFailed;
-use http\Client\Request;
-
-class Webhook extends Delete
-{
-       function getRequest() {
-               $url = $this->url->mod("./repos/". $this->args["repo"] ."/hooks/". $this->args["id"]);
-               $request = new Request("DELETE", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken(),
-               ]);
-               return $request;
-       }
-       
-       function getException($message, $code, $previous = null) {
-               return new WebhookDeleteFailed($message, $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/ContentsFetchFailed.php b/app/Github/Exception/ContentsFetchFailed.php
deleted file mode 100644 (file)
index cd2abd7..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-use app\Github\Exception\RequestException;
-
-class ContentsFetchFailed extends \Exception implements RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "Contents fetch request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/HooksFetchFailed.php b/app/Github/Exception/HooksFetchFailed.php
deleted file mode 100644 (file)
index 9094ef5..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-use app\Github\Exception\RequestException;
-
-class HooksFetchFailed extends \Exception implements RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "Hooks fetch request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/ReleaseAssetCreateFailed.php b/app/Github/Exception/ReleaseAssetCreateFailed.php
deleted file mode 100644 (file)
index 16a553d..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-use app\Github\Exception\RequestException;
-
-class ReleaseAssetCreateFailed extends \Exception implements RequestException
-{
-       function __construct($message, $code, $previous = null)
-       {
-               parent::__construct($message ?: "ReleaseAsset create request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/ReleaseCreateFailed.php b/app/Github/Exception/ReleaseCreateFailed.php
deleted file mode 100644 (file)
index d51bb24..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-use app\Github\Exception\RequestException;
-
-class ReleaseCreateFailed extends \Exception implements RequestException
-{
-       function __construct($message, $code, $previous = null)
-       {
-               parent::__construct($message ?: "Release create request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/ReleasesFetchFailed.php b/app/Github/Exception/ReleasesFetchFailed.php
deleted file mode 100644 (file)
index 12043de..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-use app\Github\Exception\RequestException;
-
-class ReleasesFetchFailed extends \Exception implements RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "Releases fetch request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/ReposFetchFailed.php b/app/Github/Exception/ReposFetchFailed.php
deleted file mode 100644 (file)
index 8cdd614..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-use app\Github\Exception\RequestException;
-
-class ReposFetchFailed extends \Exception implements RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "Repos fetch request failed", $code, $previous);
-       }
-}
index bb78982dd07b61efdad78963321ac52e38a1c149..49ea1ae72f9a5db3fa805299cb48b732bfb886e9 100644 (file)
@@ -3,7 +3,55 @@
 namespace app\Github\Exception;
 
 use app\Github\Exception;
+use http\Header;
+use http\Client\Response;
 
-interface RequestException extends Exception
+class RequestException extends \Exception implements Exception
 {
+       protected $errors = [];
+       
+       function __construct(Response $response) {
+               if (($h = $response->getHeader("Content-Type", Header::class)) 
+               &&      $h->match("application/json", Header::MATCH_WORD)
+               &&      $failure = json_decode($response->getBody())) {
+                       $message = $failure->message;
+                       if (isset($failure->errors)) {
+                               $this->errors = (array) $failure->errors;
+                       }
+               } else {
+                       $message = $response->getBody()->toString();
+               }
+               
+               if (!strlen($message)) {
+                       $message = $response->getTransferInfo("error");
+               }
+               
+               parent::__construct($message, $response->getResponseCode(), null);
+       }
+       
+       function getErrors() {
+               return $this->errors;
+       }
+       
+       function getErrorsAsString() {
+               static $reasons = [
+                       "missing" => "The resource %1\$s does not exist\n",
+                       "missing_field" => "Missing field %2\$s of resource %1\$s\n",
+                       "invalid" => "Invalid formatting of field %2\$s of resource %1\$s\n",
+                       "already_exists" => "A resource %1\$s with the same value of field %2\$s already exists\n",
+               ];
+               
+               if (!$this->errors) {
+                       return "";
+               }
+               
+               $errors = "JSON errors:\n";
+               foreach ($this->errors as $error) {
+                       $errors .= sprintf($reasons[$error->code], $error->resource, $error->field);
+               }
+               return $errors;
+       }
+       function __toString() {
+               return parent::__toString() . "\n". $this->getErrorsAsString();
+       }
 }
diff --git a/app/Github/Exception/TokenFetchFailed.php b/app/Github/Exception/TokenFetchFailed.php
deleted file mode 100644 (file)
index 128330f..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-use app\Github\Exception\TokenException;
-use app\Github\Exception\RequestException;
-
-class TokenFetchFailed extends \Exception implements TokenException, RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "Token fetch request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/UserFetchFailed.php b/app/Github/Exception/UserFetchFailed.php
deleted file mode 100644 (file)
index b43f884..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace app\Github\Exeption;
-
-class UserFetchFailed extends \Exception implements app\Github\Exception\RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "User fetch request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/WebhookCreateFailed.php b/app/Github/Exception/WebhookCreateFailed.php
deleted file mode 100644 (file)
index 2b047e2..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-class WebhookCreateFailed extends \Exception implements \app\Github\Exception\RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "Webhook create request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Exception/WebhookDeleteFailed.php b/app/Github/Exception/WebhookDeleteFailed.php
deleted file mode 100644 (file)
index aed496e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace app\Github\Exception;
-
-class WebhookDeleteFailed extends \Exception implements \app\Github\Exception\RequestException
-{
-       function __construct($message, $code, $previous = null) {
-               parent::__construct($message ?: "Webhook delete request failed", $code, $previous);
-       }
-}
diff --git a/app/Github/Fetch.php b/app/Github/Fetch.php
deleted file mode 100644 (file)
index 1a18734..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-
-namespace app\Github;
-
-use http\Header;
-use http\Params;
-use http\Url;
-
-abstract class Fetch
-{
-       /**
-        * @var \app\Github\API
-        */
-       protected $api;
-
-       /**
-        * @var \merry\Config
-        */
-       protected $config;
-
-       /**
-        * @var array
-        */
-       protected $args;
-
-       /**
-        * @var int
-        */
-       protected $page = 1;
-
-       /**
-        * @var \http\Url
-        */
-       protected $url;
-
-       function __construct(API $api, array $args = []) {
-               $this->api = $api;
-               $this->config = $api->getConfig();
-               $this->args = $args;
-               $this->url = new Url("https://api.github.com/", null, 0);
-               if (isset($this->config->fetch->{$this}->per_page)) {
-                       $this->url->query = "per_page=" . $this->config->fetch->{$this}->per_page;
-               }
-       }
-
-       function setPage($page) {
-               $this->page = $page;
-               return $this;
-       }
-
-       function getPage() {
-               return $this->page;
-       }
-
-       function __toString() {
-               $parts = explode("\\", get_class($this));
-               return strtolower(end($parts));
-       }
-       
-       function getStorage(&$ttl = null) {
-               if (isset($this->config->storage->cache->{$this}->ttl)) {
-                       $ttl = $this->config->storage->cache->{$this}->ttl;
-               }
-               return $this->api->getCacheStorage();
-       }
-
-       abstract function getRequest();
-       abstract function getException($message, $code, $previous = null);
-       abstract function getCacheKey();
-
-       function parseLinks(Header $header) {
-               $params = new Params($header->value, ",", ";", "=",
-                       Params::PARSE_RFC5988 | Params::PARSE_ESCAPED);
-               $links = [];
-               foreach ($params->params as $link => $param) {
-                       // strip enclosing brackets
-                       $links[$param["arguments"]["rel"]] = $link;
-               }
-               return $links;
-       }
-
-       function __invoke(callable $callback) {
-               $ttl = -1;
-               $key = $this->getCacheKey();
-               if (($cache = $this->api->getCacheStorage()) 
-               &&      $cache->get($key, $cached, $ttl)) {
-                       call_user_func_array($callback, $cached);
-               } else {
-                       $this->enqueue($callback);
-               }
-               return $this->api->getClient();
-       }
-
-       protected function wrap(callable $callback) {
-               return function($response) use($callback) {
-                       $rc = $response->getResponseCode();
-
-                       if ($rc !== 200) {
-                               if ($response->getHeader("Content-Type", Header::class)->match("application/json", Header::MATCH_WORD)) {
-                                       $message = json_decode($response->getBody())->message;
-                               } else {
-                                       $message = $response->getBody();
-                               }
-                               throw $this->getException($message, $rc);
-                       }
-
-                       $json = json_decode($response->getBody());
-                       if (($link = $response->getHeader("Link", Header::class))) {
-                               $links = $this->parseLinks($link);
-                       } else {
-                               $links = [];
-                       }
-
-                       if (isset($json->error)) {
-                               throw $this->getException($json->error_description, $rc);
-                       } elseif (($cache = $this->getStorage($ttl))) {
-                               $cache->set($this->getCacheKey(), [$json, $links], $ttl);
-                       }
-
-                       $callback($json, $links);
-
-                       return true;
-               };
-       }
-
-       function enqueue(callable $callback) {
-               $request = $this->getRequest();
-               $wrapper = $this->wrap($callback);
-               return $this->api->getClient()->enqueue($request, $wrapper);
-       }
-}
\ No newline at end of file
diff --git a/app/Github/Fetch/Contents.php b/app/Github/Fetch/Contents.php
deleted file mode 100644 (file)
index 955cb3c..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exception\ContentsFetchFailed;
-use app\Github\Fetch;
-use http\Client\Request;
-
-class Contents extends Fetch
-{
-       function getRequest() {
-               $url = $this->url->mod(sprintf("/repos/%s/contents/%s", 
-                       $this->args["repo"], $this->args["path"]));
-               return new Request("GET", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken()
-               ]);
-       }
-       
-       function getException($message, $code, $previous = null) {
-               return new ContentsFetchFailed($message, $code, $previous);
-       }
-       
-       function getCacheKey() {
-               return $this->api->getCacheKey(sprintf("contents:%s:%s",
-                       $this->args["repo"], $this->args["path"]));
-       }
-}
diff --git a/app/Github/Fetch/Hooks.php b/app/Github/Fetch/Hooks.php
deleted file mode 100644 (file)
index c2730e4..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exception\HooksFetchFailed;
-use app\Github\Fetch;
-
-use http\Client\Request;
-
-class Hooks extends Fetch
-{
-       function getRequest() {
-               $url = $this->url->mod([
-                       "path" => "/repos/" . $this->args["repo"] . "/hooks",
-               ]);
-               return new Request("GET", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken(),
-               ]);
-       }
-
-       function getException($message, $code, $previous = null) {
-               return new HooksFetchFailed($message, $code, $previous);
-       }
-
-       function getCacheKey() {
-               return $this->api->getCacheKey(sprintf("hooks:%s", $this->args["repo"]));
-       }
-}
diff --git a/app/Github/Fetch/Releases.php b/app/Github/Fetch/Releases.php
deleted file mode 100644 (file)
index bd9f8f9..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exception\ReleasesFetchFailed;
-use app\Github\Fetch;
-
-use http\Client\Request;
-use http\QueryString;
-
-class Releases extends Fetch
-{
-       function getRequest() {
-               $url = $this->url->mod([
-                       "path" => "/repos/" . $this->args["repo"] . "/releases",
-                       "query" => new QueryString([
-                               "page" => $this->getPage(),
-                       ])
-               ], 0);
-               return new Request("GET", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken(),
-               ]);
-       }
-
-       function getException($message, $code, $previous = null) {
-               return new ReleasesFetchFailed($message, $code, $previous);
-       }
-
-       function getCacheKey() {
-               return $this->api->getCacheKey(sprintf("releases:%s", $this->args["repo"]));
-       }
-}
\ No newline at end of file
diff --git a/app/Github/Fetch/Repo.php b/app/Github/Fetch/Repo.php
deleted file mode 100644 (file)
index a0889d6..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exception\ReposFetchFailed;
-use app\Github\Fetch;
-
-use http\Client\Request;
-
-class Repo extends Fetch
-{
-       function getRequest() {
-               $url = $this->url->mod("/repos/" . $this->args["repo"]);
-               return new Request("GET", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken(),
-               ]);
-       }
-
-       function getException($message, $code, $previous = null) {
-               return new ReposFetchFailed($message, $code, $previous);
-       }
-
-       function getCacheKey() {
-               return $this->api->getCacheKey(sprintf("repo:%s", $this->args["repo"]));
-       }
-}
diff --git a/app/Github/Fetch/Repos.php b/app/Github/Fetch/Repos.php
deleted file mode 100644 (file)
index 30b3c4c..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exception\ReposFetchFailed;
-use app\Github\Fetch;
-
-use http\Client\Request;
-use http\QueryString;
-
-class Repos extends Fetch
-{
-       function getRequest() {
-               $url = $this->url->mod([
-                       "path" => "/user/repos",
-                       "query" => new QueryString([
-                               "page" => $this->getPage()
-                       ])
-               ]);
-               return new Request("GET", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken(),
-               ]);
-       }
-
-       function getException($message, $code, $previous = null) {
-               return new ReposFetchFailed($message, $code, $previous);
-       }
-
-       function getCacheKey() {
-               return $this->api->getCacheKey("repos", $this->page);
-       }
-
-       protected function wrap(callable $callback) {
-               return parent::wrap(function($json, $links) use($callback) {
-                       if (($cache = $this->getStorage($ttl))) {
-                               foreach ($json as $repo) {
-                                       $key = $this->api->getCacheKey(sprintf("repo:%s", $repo->full_name));
-                                       $cache->set($key, [$repo, $links], $ttl);
-                               }
-                       }
-
-                       $callback($json, $links);
-
-                       return true;
-               });
-       }
-
-}
diff --git a/app/Github/Fetch/Tags.php b/app/Github/Fetch/Tags.php
deleted file mode 100644 (file)
index 9ddd606..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exception\TagsFetchFailed;
-use app\Github\Fetch;
-
-use http\Client\Request;
-use http\QueryString;
-
-class Tags extends Fetch
-{
-       function getRequest() {
-               $url = $this->url->mod([
-                       "path" => "/repos/" . $this->args["repo"] . "/tags",
-                       "query" => new QueryString([
-                               "page" => $this->getPage(),
-                       ])
-               ], 0);
-               return new Request("GET", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken(),
-               ]);
-       }
-
-       function getException($message, $code, $previous = null) {
-               return new TagsFetchFailed($message, $code, $previous);
-       }
-
-       function getCacheKey() {
-               return $this->api->getCacheKey(sprintf("tags:%s", $this->args["repo"]));
-       }
-}
\ No newline at end of file
diff --git a/app/Github/Fetch/Token.php b/app/Github/Fetch/Token.php
deleted file mode 100644 (file)
index 16a270a..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exception\TokenFetchFailed;
-use app\Github\Fetch;
-
-use http\Client\Request;
-use http\QueryString;
-
-class Token extends Fetch
-{
-       function getRequest() {
-               $request = new Request("POST", "https://github.com/login/oauth/access_token", [
-                       "Accept" => "application/json",
-               ]);
-               $request->getBody()->append(
-                       new QueryString([
-                               "client_id" => $this->args["id"],
-                               "client_secret" => $this->args["secret"],
-                               "code" => $this->args["code"]
-                       ])
-               );
-               return $request;
-       }
-
-       function getException($message, $code, $previous = null) {
-               return new TokenFetchFailed($message, $code, $previous);
-       }
-
-       function getCacheKey() {
-               return "access_token";
-       }
-
-       function getStorage(&$ttl = null) {
-               /* do not cache externally */
-               return null;
-       }
-}
\ No newline at end of file
diff --git a/app/Github/Fetch/User.php b/app/Github/Fetch/User.php
deleted file mode 100644 (file)
index 010fb5e..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-namespace app\Github\Fetch;
-
-use app\Github\Exeption\UserFetchFailed;
-use app\Github\Fetch;
-use http\Client\Request;
-
-class User extends Fetch
-{
-       function getRequest() {
-               $url = $this->url->mod("/user");
-               return new Request("GET", $url, [
-                       "Accept" => "application/vnd.github.v3+json",
-                       "Authorization" => "token " . $this->api->getToken()
-               ]);
-       }
-       
-       function getException($message, $code, $previous = null) {
-               return new UserFetchFailed($message, $code, $previous);
-       }
-       
-       function getCacheKey() {
-               return $this->api->getCacheKey("user");
-       }
-}
diff --git a/app/Github/Links.php b/app/Github/Links.php
new file mode 100644 (file)
index 0000000..7a498b2
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+
+namespace app\Github;
+
+use http\Params;
+use http\QueryString;
+use http\Url;
+
+class Links
+{
+       /**
+        * @var \http\Params
+        */
+       private $params;
+       
+       /**
+        * @var array
+        */
+       private $relations = [];
+       
+       function __construct($header_value) {
+               $this->params = new Params($header_value, ",", ";", "=",
+                       Params::PARSE_RFC5988 | Params::PARSE_ESCAPED);
+               if ($this->params->params) {
+                       foreach ($this->params->params as $link => $param) {
+                               $this->relations[$param["arguments"]["rel"]] = $link;
+                       }
+               }
+       }
+       
+       function getRelations() {
+               return $this->relations;
+       }
+       
+       function getNext() {
+               if (isset($this->relations["next"])) {
+                       return $this->relations["next"];
+               }
+               if (isset($this->relations["last"])) {
+                       return $this->relations["last"];
+               }
+               return null;
+       }
+       
+       function getPrev() {
+               if (isset($this->relations["prev"])) {
+                       return $this->relations["prev"];
+               }
+               if (isset($this->relations["first"])) {
+                       return $this->relations["first"];
+               }
+               return null;
+       }
+       
+       function getLast() {
+               if (isset($this->relations["last"])) {
+                       return $this->relations["last"];
+               }
+               return null;
+       }
+       
+       function getFirst() {
+               if (isset($this->relations["first"])) {
+                       return $this->relations["first"];
+               }
+               return null;
+       }
+       
+       function getPage($which) {
+               if (($link = $this->{"get$which"}())) {
+                       $url = new Url($link, null, 0);
+                       $qry = new QueryString($url->query);
+                       return $qry->getInt("page", 1);
+               }
+               return 1;
+       }
+}
index 5d0a51644b1bbcb6075f5d5e843556f3fb13f7e6..2a12f16b0fa3762f0984904716716d974300ecaf 100644 (file)
@@ -1,6 +1,7 @@
 
 <div class="alert alert-danger" role="alert">
-       <strong><?= nl2br($this->e($exception->getMessage())) ?></strong><br>
+       <strong><?= nl2br($this->e($exception->getMessage())) ?></strong>
+       (<?= $exception->getCode() ?>)<br>
        <?php if ($exception instanceof \app\Github\Exception\TokenException) : ?>
                You might want to try to <a href="<?= $baseUrl->mod("./github/signin") ?>">renew your token</a>!
        <?php endif; ?>
@@ -12,4 +13,6 @@
 <?php if (APP_ENVIRONMENT != "production") : ?>
 <h3>Stack Trace</h3>
 <pre><?= $this->e($exception->getTraceAsString()) ?></pre>
+<h3>Full Dump</h3>
+<pre><?= $this->e($exception) ?></pre>
 <?php endif; ?>
index f3fdf46d4f47bdbf91107314bbedf74a60caa212..fae0bae05f94319ade0b5458e0ae16579e956bbd 100644 (file)
@@ -1,5 +1,2 @@
 <?php $this->layout("layout") ?>
 
-<?php if (isset($exception)) : ?>
-       <?= $this->fetch("alert") ?>
-<?php endif; ?>
index bba0e7b43103bdab40e7b76f732b54811d84d1ee..39a2f93ba82a604b4e0993cdf5abd584c7e41f29 100644 (file)
@@ -7,10 +7,6 @@
        </h1>
 </div>
 
-<?php if (isset($exception)) : ?>
-       <?= $this->fetch("alert") ?>
-<?php elseif (isset($repos)) : ?>
-
 <table class="table table-hover">
        <thead>
                <tr>
 
 <nav>
        <ul class="pager">
-               <?php
-               $first= $this->link("first");
-               $prev = $this->link("prev");
-               $next = $this->link("next");
-               $last = $this->link("last");
-               ?>
-               <li class="<?= $first ?"":"disabled" ?>"><a href="?page=<?= $first ?>" title="First Page"><span class="glyphicon glyphicon-fast-backward"></span></a></li>
-               <li class="<?= $prev ?"":"disabled" ?>"><a href="?page=<?= $prev ?>" rel="prev" title="Previous Page"><span class="glyphicon glyphicon-chevron-left"></span> Previous</a></li>
-               <li class="<?= $next ?"":"disabled" ?>"><a href="?page=<?= $next ?>" rel="next" title="Next Page">Next <span class="glyphicon glyphicon-chevron-right"></span></a></li>
-               <li class="<?= $last ?"":"disabled" ?>"><a href="?page=<?= $last ?>" title="Last Page"><span class="glyphicon glyphicon-fast-forward"></span></a></li>
+               <li class="<?= $links->getFirst() ?"":"disabled" ?>"><a href="?page=<?= $links->getPage("first") ?>" title="First Page"><span class="glyphicon glyphicon-fast-backward"></span></a></li>
+               <li class="<?= $links->getPrev() ?"":"disabled" ?>"><a href="?page=<?= $links->getPage("prev") ?>" rel="prev" title="Previous Page"><span class="glyphicon glyphicon-chevron-left"></span> Previous</a></li>
+               <li class="<?= $links->getNext() ?"":"disabled" ?>"><a href="?page=<?= $links->getPage("next") ?>" rel="next" title="Next Page">Next <span class="glyphicon glyphicon-chevron-right"></span></a></li>
+               <li class="<?= $links->getLast() ?"":"disabled" ?>"><a href="?page=<?= $links->getPage("last") ?>" title="Last Page"><span class="glyphicon glyphicon-fast-forward"></span></a></li>
        </ul>
 </nav>
-<?php endif; ?>
index d32183068e95ff43e214e3daa57dfe250f1e8064..1b7a640bcc76297866c489d337479ade12980210 100644 (file)
        </h1>
 </div>
 
-<?php if (isset($exception)) : ?>
-       <?= $this->fetch("alert") ?>
-<?php elseif (isset($repo)) : ?>
-
 <div class="row">
        <div class="col-md-6">
                <div class="well">
 </div>
 <?php endforeach; ?>
 
-<?php endif; ?>
index 1cdc655a37f08abd42591a3e3a0e7d4963143602..ec01dbf4adcd3523da4dbaad03c48b6d6bc744bc 100644 (file)
@@ -1,12 +1,15 @@
 [production]
 
+github.api.url = https://api.github.com/
+github.api.accept = application/vnd.github.v3+json
+
+github.api.call.listrepos.args.per_page = 10
+github.api.call.listhooks.args.per_page = 100
+
 github.hook.url = https://pharext.org/github/hook
 github.hook.content_type = json
 github.hook.insecure_ssl = 0
 
-github.fetch.repos.per_page = 10
-github.fetch.hooks.per_page = 100
-
 github.storage.token.ttl = 3600
 github.storage.cache.repo.ttl = 3600
 github.storage.cache.repos.ttl = 3600