webhook create&delete
authorMichael Wallner <mike@php.net>
Mon, 4 May 2015 10:22:37 +0000 (12:22 +0200)
committerMichael Wallner <mike@php.net>
Mon, 4 May 2015 10:22:37 +0000 (12:22 +0200)
14 files changed:
app/Controller/Github.php
app/Controller/Github/Repo/Hook.php [new file with mode: 0644]
app/Github/API.php
app/Github/Create.php [new file with mode: 0644]
app/Github/Create/Webhook.php [new file with mode: 0644]
app/Github/Delete.php [new file with mode: 0644]
app/Github/Delete/Webhook.php [new file with mode: 0644]
app/Github/Exception/UserFetchFailed.php
app/Github/Exception/WebhookCreateFailed.php [new file with mode: 0644]
app/Github/Exception/WebhookDeleteFailed.php [new file with mode: 0644]
app/Web.php
app/routes.ini
app/views/github/index.phtml
app/views/github/repo.phtml

index 0dfc5b00d0e09ea017a2e536a95219beead41216..6432c868670550c0355fdaad16e7a4e9087e6e2b 100644 (file)
@@ -49,15 +49,20 @@ abstract class Github implements Controller
                return false;
        }
 
+       /**
+        * Check if the pharext webhook is set for the repo and return its id
+        * @param object $repo
+        * @return int hook id
+        */
        function checkRepoHook($repo) {
                if ($repo->hooks) {
                        foreach ($repo->hooks as $hook) {
                                if ($hook->name === "web" && $hook->config->url === $this->github->getConfig()->hook->url) {
-                                       return true;
+                                       return $hook->id;
                                }
                        }
                }
-               return false;
+               return null;
        }
 
        function createLinkGenerator($links) {
diff --git a/app/Controller/Github/Repo/Hook.php b/app/Controller/Github/Repo/Hook.php
new file mode 100644 (file)
index 0000000..7bfb545
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace app\Controller\Github\Repo;
+
+use app\Controller\Github;
+
+class Hook extends Github
+{
+       function __invoke(array $args = null) {
+               switch ($args["action"]) {
+                       case "add":
+                               $this->addHook($args["owner"], $args["name"]);
+                               break;
+                       
+                       case "del":
+                               $this->delHook($args["owner"], $args["name"]);
+                               break;
+               }
+       }
+       
+       function addHook($owner, $repo) {
+               $this->github->createRepoHook("$owner/$repo", function($hook) use($owner, $repo) {
+                       if (($cache = $this->github->getCacheStorage())) {
+                               $cache->del($this->github->getCacheKey("hooks:$owner/$repo"));
+                       }
+                       if (($back = $this->app->getRequest()->getForm("returnback")) && isset($this->session->previous)) {
+                               $this->app->redirect($this->app->getBaseUrl()->mod($this->session->previous));
+                       } else {
+                               $this->app->redirect($this->app->getBaseUrl()->mod("./github/repo/$owner/$repo"));
+                       }
+               })->send();
+       }
+       
+       function delHook($owner, $repo) {
+               $this->github->fetchRepo("$owner/$repo", function($repo) {
+                       $this->github->fetchHooks($repo->full_name, function($hooks) use($repo) {
+                               $repo->hooks = $hooks;
+                               if (($id = $this->checkRepoHook($repo))) {
+                                       $this->github->deleteRepoHook($repo->full_name, $id, function() use($repo) {
+                                               if (($cache = $this->github->getCacheStorage())) {
+                                                       $cache->del($this->github->getCacheKey("hooks:" . $repo->full_name));
+                                               }
+                                               if (($back = $this->app->getRequest()->getForm("returnback")) && isset($this->session->previous)) {
+                                                       $this->app->redirect($this->app->getBaseUrl()->mod($this->session->previous));
+                                               } else {
+                                                       $this->app->redirect($this->app->getBaseUrl()->mod("./github/repo/" . $repo->full_name));
+                                               }
+                                       });
+                               }
+                       });
+               })->send();
+       }
+}
index 10cf2c0c33640f90f9dfbbc360b69366f4d0c000..f62bf06cc1dc9659c4fdfe62de4c474ca23a247f 100644 (file)
@@ -149,4 +149,14 @@ class API
                $fetch = new Fetch\Contents($this, compact("repo", "path"));
                return $fetch($callback);
        }
+       
+       function createRepoHook($repo, callable $callback) {
+               $create = new Create\Webhook($this, compact("repo"));
+               return $create($callback);
+       }
+       
+       function deleteRepoHook($repo, $id, callable $callback) {
+               $delete = new Delete\Webhook($this, compact("repo", "id"));
+               return $delete($callback);
+       }
 }
diff --git a/app/Github/Create.php b/app/Github/Create.php
new file mode 100644 (file)
index 0000000..0f3f31a
--- /dev/null
@@ -0,0 +1,80 @@
+<?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/Webhook.php b/app/Github/Create/Webhook.php
new file mode 100644 (file)
index 0000000..6e583fc
--- /dev/null
@@ -0,0 +1,33 @@
+<?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(),
+               ]);
+               $request->getBody()->append(json_encode([
+                       "name" => "web",
+                       "config" => [
+                               "url" => $this->config->hook->url,
+                               "content_type" => $this->config->hook->content_type,
+                               "secret" => $this->config->client->secret, // FIXME: bad idea?
+                               "insecure_ssl" => false,
+                       ]
+               ]));
+               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
new file mode 100644 (file)
index 0000000..4e859a6
--- /dev/null
@@ -0,0 +1,74 @@
+<?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
new file mode 100644 (file)
index 0000000..5493872
--- /dev/null
@@ -0,0 +1,23 @@
+<?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);
+       }
+}
index 578e106411ec70880c467c00e0cb46e83bf5a2be..b43f884616c204047afd8fbcfb6e6b0fd4769f01 100644 (file)
@@ -2,7 +2,7 @@
 
 namespace app\Github\Exeption;
 
-class UserFetchFailed extends Exception implements app\Github\Exception\RequestException
+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
new file mode 100644 (file)
index 0000000..2b047e2
--- /dev/null
@@ -0,0 +1,10 @@
+<?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
new file mode 100644 (file)
index 0000000..aed496e
--- /dev/null
@@ -0,0 +1,10 @@
+<?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);
+       }
+}
index e9d5d1659e9d59481b5c60239e4ac0bb2e2f74d5..39cfe8d8e4ccd877f9b59401d94f159b6a5939e3 100644 (file)
@@ -20,7 +20,7 @@ class Web
                $this->baseUrl = $baseUrl;
                $this->request = $request;
                $this->response = $response;
-               $this->view = $view;
+               $this->view = $view->addData(["location" => null]);
                ob_start($response);
        }
 
index f9954d8c0948d205ef6a37cb118d6f2bf92c5a49..40782bf8efd45790f9fc68bee200b64c30857308 100644 (file)
@@ -14,6 +14,9 @@ GET[] = /github
 GET[] = /github/repo/{owner}/{name}
 GET[] = /github/repo/{owner}/{name}/{page}
 
+[Github\Repo\Hook]
+POST[] = /github/repo/{owner}/{name}/hook/{action}
+
 [Github\Signin]
 GET[] = /github/signin
 
index 987ea8e4c63e9782aacc89e4c07e046a4c55d2d5..bba0e7b43103bdab40e7b76f732b54811d84d1ee 100644 (file)
@@ -17,7 +17,7 @@
                        <th>Repo</th>
                        <th class="text-center">Fork</th>
                        <th class="text-right">Last Pushed</th>
-                       <th class="text-right">Hook</th>
+                       <th class="text-center">Hook</th>
                </tr>
        </thead>
        <tbody>
                                <?php endif; ?>
                        </td>
                        <td class="text-right"><time datetime="<?= $this->e($repo->pushed_at) ?>"><?= $this->e($this->utc($repo->pushed_at)->format("Y-m-d H:i T")) ?></time></td>
-                       <td class="text-right">
-                               <a class="btn btn-primary <?= $this->check($repo) ? "disabled":"" ?>"
-                                  href="<?= $baseUrl->mod("./github/repo/".$repo->full_name) ?>">
-                                       <span class="glyphicon glyphicon-plus" title="Add pharext hook"></span></a>
-                               <a class="btn btn-danger <?= !$this->check($repo) ? "disabled":"" ?>"
-                                  href="<?= $baseUrl->mod(["path" => "github/repo", "query" => "hook=del&repo=". urlencode($repo->full_name)]) ?>">
-                                       <span class="glyphicon glyphicon-remove" title="Remove hook"></span></a>
+                       <td class="text-center">
+                               <form class="form-inline" style="display: inline-block" method="post" action="<?= $baseUrl->mod("./github/repo/". $repo->full_name ."/hook/add") ?>">
+                                       <input type="hidden" name="returnback" value="1">
+                                       <button type="submit" class="btn btn-primary <?= $this->check($repo) ? "disabled":"" ?>">
+                                               <span class="glyphicon glyphicon-plus" title="Add pharext hook"></span>
+                                       </button>
+                               </form>
+                               <form class="form-inline" style="display: inline-block" method="post" action="<?= $baseUrl->mod("./github/repo/". $repo->full_name ."/hook/del") ?>"> 
+                                       <input type="hidden" name="returnback" value="1">
+                                       <button type="submit" class="btn btn-danger <?= $this->check($repo) ? "":"disabled" ?>">
+                                               <span class="glyphicon glyphicon-remove" title="Remove hook"></span>
+                                       </button>
+                               </form>
+                       </div>
                        </td>
                </tr>
                <?php endforeach; ?>
index 58721bb570fdf3eb7c3b1e33273e8c5031e6973c..372de5d5a6c65234e151185c037060064415b700 100644 (file)
        </div>
 
        <div class="col-md-6">
+               
                <div class="row text-center">
                        <div class="col-md-6">
-                               <button class="btn btn-lg btn-block btn-success <?= $this->check($repo) ? "disabled":"" ?>">Enable Hook</button>
+                               <form method="post" action="<?= $baseUrl->mod("./github/repo/". $repo->full_name ."/hook/add") ?>"> 
+                               <button type="submit" class="btn btn-lg btn-block btn-success <?= $this->check($repo) ? "disabled":"" ?>">Enable Hook</button>
+                               </form>
                        </div>
                        <div class="col-md-6">
+                               <form method="post" action="<?= $baseUrl->mod("./github/repo/". $repo->full_name ."/hook/del") ?>"> 
                                <button class="btn btn-lg btn-block btn-danger <?= $this->check($repo) ? "":"disabled" ?>">Disable Hook</button>
+                               </form>
                        </div>
                </div>
+               </form>
        </div>
 </div>