flush
authorMichael Wallner <mike@php.net>
Fri, 8 May 2015 10:12:09 +0000 (12:12 +0200)
committerMichael Wallner <mike@php.net>
Fri, 8 May 2015 10:12:09 +0000 (12:12 +0200)
52 files changed:
.gitignore
app/Cli.php
app/Cli/GenModels.php [new file with mode: 0644]
app/Cli/Initdb.php [new file with mode: 0644]
app/Cli/Ngrok.php [new file with mode: 0644]
app/Config.php [new file with mode: 0644]
app/Controller/Github.php
app/Controller/Github/Callback.php
app/Controller/Github/Hook.php
app/Controller/Github/Repo/Hook.php
app/Controller/Github/Signin.php
app/Github/API.php
app/Github/Create/Release.php [new file with mode: 0644]
app/Github/Create/ReleaseAsset.php [new file with mode: 0644]
app/Github/Create/Webhook.php
app/Github/Exception/ReleaseAssetCreateFailed.php [new file with mode: 0644]
app/Github/Exception/ReleaseCreateFailed.php [new file with mode: 0644]
app/Github/Fetch.php
app/Model/Account.php [new file with mode: 0644]
app/Model/AccountCollection.php [new file with mode: 0644]
app/Model/Accounts.php [new file with mode: 0644]
app/Model/Authorities.php [new file with mode: 0644]
app/Model/Authority.php [new file with mode: 0644]
app/Model/AuthorityCollection.php [new file with mode: 0644]
app/Model/Owner.php [new file with mode: 0644]
app/Model/OwnerCollection.php [new file with mode: 0644]
app/Model/Owners.php [new file with mode: 0644]
app/Model/Token.php [new file with mode: 0644]
app/Model/TokenCollection.php [new file with mode: 0644]
app/Model/Tokens.php [new file with mode: 0644]
app/Session.php
app/bootstrap/cli.php
app/bootstrap/config.php
app/bootstrap/github.php
app/bootstrap/http.php [new file with mode: 0644]
app/bootstrap/model.php [new file with mode: 0644]
app/bootstrap/plates.php
app/bootstrap/pq.php [new file with mode: 0644]
app/bootstrap/router.php [new file with mode: 0644]
app/bootstrap/session.php [new file with mode: 0644]
app/bootstrap/web.php
app/config.ini [deleted file]
app/routes.ini [deleted file]
app/views/github/repo.phtml
app/views/navbar.phtml
bin/pharext.org
composer.json
composer.lock
config/app.ini [new file with mode: 0644]
config/routes.ini [new file with mode: 0644]
config/sql/001.sql [new file with mode: 0644]
public/index.php

index b551a21c7c25bf7328367dabb090bdd22c557e5a..cfc2631dc7959c24debd2f44e914668085a8182b 100644 (file)
@@ -1,3 +1,4 @@
 vendor
 nbproject
 app/credentials.ini
+config/credentials.ini
index 114bfed50097e8a589e2ef62ba627bad8885ca35..56dbc5c9bc2a617aca94b5e01b7e64d5bd53bf12 100644 (file)
@@ -2,22 +2,26 @@
 
 namespace app;
 
-use merry\Config;
 use pharext\Cli\Args;
 
 class Cli
 {
        /**
-        * @var \merry\Config
+        * @var \app\Config
         */
        private $config;
        
+       /**
+        * @var \pharext\Cli\Args
+        */
+       private $args;
+       
        function __construct(Config $config, Args $args) {
                $this->config = $config;
                $this->args = $args;
        }
        
-       function __invoke($argc, array $argv) {
+       function __invoke($argc, array $argv, callable $exec) {
                $prog = array_shift($argv);
                foreach ($this->args->parse(--$argc, $argv) as $error) {
                        $errs[] = $error;
@@ -35,15 +39,13 @@ class Cli
                }
                
                if ($this->args["ngrok"]) {
-                       system($this->config->ngrok->command . " ". implode(" ", array_map("escapeshellarg", [
-                               "http",
-                               "--subdomain=pharext",
-                               "--authtoken",
-                               $this->config->ngrok->auth->token,
-                               "--auth",
-                               $this->config->ngrok->auth->user .":". $this->config->ngrok->auth->pass,
-                               "80"
-                       ])));
+                       $exec(Cli\Ngrok::class);
+               }
+               if ($this->args["initdb"]) {
+                       $exec(Cli\Initdb::class);
+               }
+               if ($this->args["gen-models"]) {
+                       $exec(Cli\GenModels::class);
                }
        }
        
diff --git a/app/Cli/GenModels.php b/app/Cli/GenModels.php
new file mode 100644 (file)
index 0000000..9f1b824
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+
+namespace app\Cli;
+
+use app\Controller;
+use app\Model;
+use pq\Connection;
+
+class GenModels implements Controller
+{
+       private $pq;
+       
+       function __construct(Connection $pq) {
+               $this->pq = $pq;
+       }
+       
+       function __invoke(array $args = null) {
+               $tables = $this->pq->exec("SELECT tablename FROM pg_tables WHERE schemaname='public'");
+               /* @var $tables \pq\Result */
+               foreach ($tables->fetchAllCols("tablename") as $table) {
+                       $this->genModel($table);
+               }
+       }
+       
+       function genModel($entity) {
+               $title = ucwords($entity);
+               $single = substr($title, -3) == "ies" 
+                       ? substr($title, 0, -3)."y" 
+                       : rtrim($title, "s");
+               $this->genTable($entity, $title, $single."Collection");
+               $this->genRowset($single."Collection", $single);
+               $this->genRow($single);
+       }
+       
+       function genTable($name, $class, $collection) {
+               $ns = explode("\\", $class);
+               
+               if (count($ns) > 1) {
+                       $class = array_pop($ns);
+                       $dir = implode("/", $ns);
+                       $ns = "\\".implode("\\", $ns);
+               } else {
+                       $ns = "";
+                       $dir = "";
+               }
+               
+               $file = __DIR__."/../Model/$dir/$class.php";
+               
+               if (!file_exists($file)) {
+                       file_put_contents($file, <<<EOD
+<?php
+
+namespace app\\Model$ns;
+
+use pq\\Gateway\\Table;
+
+class $class extends Table
+{
+       protected \$name = "$name";
+       protected \$rowset = "app\\\\Model$ns\\\\$collection";
+}
+
+EOD
+                       );
+               }
+       }
+       
+       function genRowset($class, $row) {
+               $ns = explode("\\", $class);
+               
+               if (count($ns) > 1) {
+                       $class = array_pop($ns);
+                       $dir = implode("/", $ns);
+                       $ns = "\\".implode("\\", $ns);
+               } else {
+                       $ns = "";
+                       $dir = "";
+               }
+               
+               $file = __DIR__."/../Model/$dir/$class.php";
+               
+               if (!file_exists($file)) {
+                       file_put_contents($file, <<<EOD
+<?php
+
+namespace app\\Model$ns;
+
+use pq\\Gateway\\Rowset;
+
+class $class extends Rowset
+{
+       protected \$row = "app\\\\Model$ns\\\\$row";
+}
+
+EOD
+                       );
+               }
+       }
+       function genRow($class) {
+               $ns = explode("\\", $class);
+               
+               if (count($ns) > 1) {
+                       $class = array_pop($ns);
+                       $dir = implode("/", $ns);
+                       $ns = "\\".implode("\\", $ns);
+               } else {
+                       $ns = "";
+                       $dir = "";
+               }
+               
+               $file = __DIR__."/../Model/$dir/$class.php";
+               
+               if (!file_exists($file)) {
+                       file_put_contents($file, <<<EOD
+<?php
+
+namespace app\\Model$ns;
+
+use pq\\Gateway\\Row;
+
+class $class extends Row
+{
+}
+
+EOD
+                       );
+               }
+       }
+       
+}
\ No newline at end of file
diff --git a/app/Cli/Initdb.php b/app/Cli/Initdb.php
new file mode 100644 (file)
index 0000000..77de015
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace app\Cli;
+
+use app\Controller;
+use pq\Connection;
+
+class Initdb implements Controller
+{
+       private $pq;
+       
+       function __construct(Connection $pq) {
+               $this->pq = $pq;
+       }
+       
+       function __invoke(array $args = null) {
+               foreach (glob(__DIR__."/../../config/sql/*.sql") as $sql) {
+                       $xa = $this->pq->startTransaction();
+                       $this->pq->exec(file_get_contents($sql));
+                       $xa->commit();
+               }
+       }
+}
diff --git a/app/Cli/Ngrok.php b/app/Cli/Ngrok.php
new file mode 100644 (file)
index 0000000..82236c2
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace app\Cli;
+
+use app\Config;
+use app\Controller;
+
+class Ngrok implements Controller
+{
+       private $config;
+       
+       function __construct(Config $config) {
+               $this->config = $config;
+       }
+       
+       function __invoke(array $args = null) {
+               system($this->config->ngrok->command . " ". implode(" ", array_map("escapeshellarg", [
+                       "http",
+                       "--subdomain=pharext",
+                       "--log=stderr",
+                       "--authtoken",
+                       $this->config->ngrok->auth->token,
+                       "--auth",
+                       $this->config->ngrok->auth->user .":". $this->config->ngrok->auth->pass,
+                       "80"
+               ])));
+       }
+}
\ No newline at end of file
diff --git a/app/Config.php b/app/Config.php
new file mode 100644 (file)
index 0000000..2d97da2
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+namespace app;
+
+use merry\Config as Container;
+
+class Config extends Container
+{
+       function addConfig(Config $config) {
+               foreach ($config as $sub => $conf) {
+                       /* one level down should suffice, i.e. $config->github->client = {id,secret,scope} */
+                       if ($conf instanceof Config) {
+                               foreach ($conf as $key => $val) {
+                                       $this->$sub->$key = $val;
+                               }
+                       } else {
+                               $this->$sub = $conf;
+                       }
+               }
+       }
+}
index 6432c868670550c0355fdaad16e7a4e9087e6e2b..5933fcd2ffc12aee7ccf5ef74a79daaea9e6e3dd 100644 (file)
@@ -26,7 +26,7 @@ abstract class Github implements Controller
         * @var \app\Session
         */
        protected $session;
-
+       
        function __construct(Web $app, API $github, Session $session) {
                $this->app = $app;
                $this->github = $github;
index 5b62faa3251cb9e234c34205119c95879b065f2c..d6d11920c6bd14ab3ecd465356536b1b49e94175 100644 (file)
@@ -3,9 +3,25 @@
 namespace app\Controller\Github;
 
 use app\Controller\Github;
+use app\Github\API;
+use app\Github\Exception;
+use app\Model\Accounts;
+use app\Session;
+use app\Web;
 
 class Callback extends Github
 {
+       
+       /**
+        * @var Accounts
+        */
+       private $accounts;
+       
+       function __construct(Web $app, API $github, Session $session, Accounts $accounts) {
+               parent::__construct($app, $github, $session);
+               $this->accounts = $accounts;
+       }
+       
        function __invoke(array $args = null) {
                if ($this->app->getRequest()->getQuery("error")) {
                        $this->app->getView()->addData([
@@ -16,22 +32,39 @@ class Callback extends Github
                                $this->github->fetchToken(
                                        $this->app->getRequest()->getQuery("code"),
                                        $this->app->getRequest()->getQuery("state"),
-                                       function($json) {
-                                               $this->github->setToken($json->access_token);
-                                               $this->github->fetchUser(function($user) {
-                                                       $this->session->github = $user;
-                                               });
+                                       function($token) {
+                                               $this->github->setToken($token->access_token);
+                                               $this->github->fetchUser($this->createUserCallback($token));
                                })->send();
                                if (isset($this->session->returnto)) {
-                                       $this->app->redirect($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 (\app\Github\Exception $exception) {
+                       } catch (Exception $exception) {
                                $this->app->getView()->addData(compact("exception"));
                        }
                }
                $this->app->display("github/callback");
        }
+       
+       function createUserCallback($token) {
+               return function($user) use($token) {
+                       $tx = $this->accounts->getConnection()->startTransaction();
+                       
+                       if (!($account = $this->accounts->byOAuth("github", $token->access_token, $user->login))) {
+                               $account = $this->accounts->createOAuthAccount("github", $token->access_token, $user->login);
+                       }
+                       $account->updateToken("github", $token->access_token, $token);
+                       $owner = $account->updateOwner("github", $user->login, $user);
+                       
+                       $tx->commit();
+                       
+                       $this->session->account = $account->account->get();
+                       $this->session->github = (object) $owner->export();
+               };
+       }
 }
index 946bb0cc4c8d215aff03ee0af9b3485256dd214b..8161dde9b83ded19f65c8a1b629dcbd5930d470c 100644 (file)
 <?php
 
-namespace app\Github\API;
+namespace app\Controller\Github;
 
 use app\Controller;
 use app\Github\API;
+use app\Model\Accounts;
 use app\Web;
+use http\Params;
+use pharext\Task;
+use pharext\SourceDir\Git;
+
+require_once __DIR__."/../../../vendor/m6w6/pharext/src/pharext/Version.php";
 
 class Hook implements Controller
 {
        private $app;
        private $github;
+       private $accounts;
        
-       function __construct(Web $app, API $github) {
+       function __construct(Web $app, API $github, Accounts $accounts) {
                $this->app = $app;
                $this->github = $github;
+               $this->accounts = $accounts;
        }
        
        function __invoke(array $args = []) {
                $request = $this->app->getRequest();
                $response = $this->app->getResponse();
                
-               if (!($sig = $request->getHeader("X-Github-Signature")) || !($evt = $request->getHeader("X-Github-Event"))) {
+               if (!($sig = $request->getHeader("X-Hub-Signature")) || !($evt = $request->getHeader("X-Github-Event"))) {
                        $response->setResponseCode(400);
-               } elseif ($sig !== hash_hmac("sha1", $request->getBody(), $this->app->getConfig()->github->client->secret)) {
-                       $response->setResponseCode(403);
-               } elseif ($evt === "ping") {
-                       $response->setReponseStatus("PONG");
-               } elseif ($evt !== "push") {
-                       $this->app->getResponse()->setResponseCode(204);
+                       $response->setContentType("message/http");
+                       $response->getBody()->append($request);
                } else {
-                       $push = json_decode($request->getBody());
+                       $key = $this->github->getConfig()->client->secret;
+                       foreach ((new Params($sig))->params as $algo => $mac) {
+                               if ($mac["value"] !== hash_hmac($algo, $request->getBody(), $key)) {
+                                       $response->setResponseCode(403);
+                                       $response->getBody()->append("Invalid signature");
+                                       return;
+                               }
+                       }
+               }
+
+               switch ($evt) {
+                       default:
+                               $response->setResponseCode(202);
+                               $response->getBody()->append("Not a configured event");
+                               break;
+                       case "ping";
+                               $response->setResponseCode(204);
+                               $response->setResponseStatus("PONG");
+                               break;
+                       case "create":
+                       case "release":
+                               if (($json = json_decode($request->getBody()))) {
+                                       $this->$evt($json);
+                               } else {
+                                       $response->setResponseCode(415);
+                                       $response->setContentType($request->getHeader("Content-Type"));
+                                       $response->getBody()->append($request->getBody());
+                               }
+                               break;
+               }
+       }
+       
+       function release($release) {
+               if ($release->action !== "published") {
+                       $response = $this->app->getResponse();
+                       
+                       $response->setResponseCode(202);
+                       $response->getBody()->append("Not published");
+                       return;
                }
+               
+               $this->uploadAssetForRelease($release->release, $release->repository);
+       }
+       
+       private function uploadAssetForRelease($release, $repo) {
+               $this->setTokenForUser($repo->owner->login);
+               $asset = $this->createReleaseAsset($release, $repo);
+               $this->github->createReleaseAsset($release->upload_url, $asset, "application/phar", function($json) {
+                       $response = $this->app->getResponse();
+                       $response->setResponseCode(201);
+                       $response->setHeader("Location", $json->url);
+               })->send();
+       }
+       
+       private function createReleaseAsset($release, $repo) {
+               define("STDERR", fopen("/var/log/apache2/php_errors.log", "a"));
+               $source = (new Task\GitClone($repo->clone_url, $release->tag_name))->run();
+               $iterator = new Git($source);
+               $meta = [
+                       "header" => sprintf("pharext v%s (c) Michael Wallner <mike@php.net>", \pharext\VERSION),
+                       "version" => \pharext\VERSION,
+                       "date" => date("Y-m-d"),
+                       "name" => $repo->name,
+                       "release" => $release->tag_name,
+                       "license" => @file_get_contents(current(glob($iterator->getBaseDir()."/LICENSE*"))),
+                       "stub" => "pharext_installer.php",
+                       "type" => false ? "zend_extension" : "extension",
+               ];
+               $file = (new Task\PharBuild($iterator, $meta))->run();
+               return $file;
+       }
+       
+       function create($create) {
+               if ($create->ref_type !== "tag") {
+                       $response = $this->app->getResponse();
+                       
+                       $response->setResponseCode(202);
+                       $response->getBody()->append("Not a tag");
+                       return;
+               }
+               
+               $this->createReleaseFromTag($create->ref, $create->repository);
+       }
+       
+       private function setTokenForUser($login) {
+               $relations = [
+                       $this->accounts->getTokens()->getRelation("accounts"),
+                       $this->accounts->getOwners()->getRelation("accounts")
+               ];
+               $tokens = $this->accounts->getTokens()->with($relations, [
+                       "login=" => $login,
+                       "tokens.authority=" => "github",
+               ]);
+               
+               if (count($tokens)) {
+                       $this->github->setToken($tokens->current()->token->get());
+               }
+       }
+       
+       private function createReleaseFromTag($tag, $repo) {
+               $this->setTokenForUser($repo->owner->login);
+               $this->github->createRelease($repo->full_name, $tag, function($json) {
+                       $response = $this->app->getResponse();
+                       $response->setResponseCode(201);
+                       $response->setHeader("Location", $json->url);
+               })->send();
        }
 }
index 1ef33a37cb921cae11284f1c8d95a5c2910a8d8b..fac64502fa58d64fa9dafa8656ac79377b30cd68 100644 (file)
@@ -15,6 +15,10 @@ class Hook extends Github
                                        "query" => "modal=hook&hook=" . $args["action"]
                                ]));
                        } else switch ($args["action"]) {
+                               case "upd":
+                                       $this->updateHook($args["owner"], $args["name"]);
+                                       break;
+                               
                                case "add":
                                        $this->addHook($args["owner"], $args["name"]);
                                        break;
@@ -27,7 +31,8 @@ class Hook extends Github
        }
        
        function addHook($owner, $repo) {
-               $this->github->createRepoHook("$owner/$repo", function($hook) use($owner, $repo) {
+               $hook_conf = $this->app->getRequest()->getForm();
+               $this->github->createRepoHook("$owner/$repo", $hook_conf, function($hook) use($owner, $repo) {
                        if (($cache = $this->github->getCacheStorage())) {
                                $cache->del($this->github->getCacheKey("hooks:$owner/$repo"));
                        }
index 05f3e3c0f84320cf7e14632effe2f4c85e9bb53e..9fca55481886af5321fe0157f4206cd55bb5a611 100644 (file)
@@ -10,7 +10,7 @@ class Signin extends Github
                $callback = $this->app->getBaseUrl()->mod("./github/callback");
                $location = $this->github->getAuthUrl($callback);
                $this->app->redirect($location);
-               if ($returnto = $this->app->getRequest()->getQuery("returnto")) {
+               if (($returnto = $this->app->getRequest()->getQuery("returnto"))) {
                        $this->session->returnto = $returnto;
                }
        }
index f62bf06cc1dc9659c4fdfe62de4c474ca23a247f..8737c16ce5a9edeccf692baed7ec8ad04068d3f4 100644 (file)
@@ -150,13 +150,28 @@ class API
                return $fetch($callback);
        }
        
-       function createRepoHook($repo, callable $callback) {
-               $create = new Create\Webhook($this, compact("repo"));
+       function createRepoHook($repo, $conf, callable $callback) {
+               $create = new Create\Webhook($this, compact("repo", "conf"));
                return $create($callback);
        }
        
+       function updateRepoHook($repo, $id, $conf, callable $callback) {
+               $update = new Update\Webhook($this, compact("repo", "id", "conf"));
+               return $update($callback);
+       }
+       
        function deleteRepoHook($repo, $id, callable $callback) {
                $delete = new Delete\Webhook($this, compact("repo", "id"));
                return $delete($callback);
        }
+       
+       function createRelease($repo, $tag, callable $callback) {
+               $create = new Create\Release($this, compact("repo", "tag"));
+               return $create($callback);
+       }
+       
+       function createReleaseAsset($url, $asset, $type, callable $callback) {
+               $create = new Create\ReleaseAsset($this, compact("url", "asset", "type"));
+               return $create($callback);
+       }
 }
diff --git a/app/Github/Create/Release.php b/app/Github/Create/Release.php
new file mode 100644 (file)
index 0000000..3f9f710
--- /dev/null
@@ -0,0 +1,27 @@
+<?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
new file mode 100644 (file)
index 0000000..03d0d41
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace app\Github\Create;
+
+use app\Github\Create;
+use app\Github\Exception\ReleaseAssetCreateFailed;
+use http\Client\Request;
+
+class ReleaseAsset extends Create
+{
+       function getRequest() {
+               // FIXME: use uri_template extension
+               $url = str_replace("{?name}", "?name=".urlencode(basename($this->args["asset"])), $this->args["url"]);
+               $body = new \http\Message\Body(fopen($this->args["asset"], "rb"));
+               $request = new Request("POST", $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);
+       }
+}
index 6f69d0c811ad2fe7ea73fac197b2ac55f0a98391..72ff2d705e8016d843fdbc71568113b203eabb4a 100644 (file)
@@ -16,8 +16,16 @@ class Webhook extends Create
                        "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,
diff --git a/app/Github/Exception/ReleaseAssetCreateFailed.php b/app/Github/Exception/ReleaseAssetCreateFailed.php
new file mode 100644 (file)
index 0000000..16a553d
--- /dev/null
@@ -0,0 +1,13 @@
+<?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
new file mode 100644 (file)
index 0000000..d51bb24
--- /dev/null
@@ -0,0 +1,13 @@
+<?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);
+       }
+}
index f0def29935ed1e3817324aa88d9ce5fe6b0c961c..1a18734b64eb91e482395dda9009147cd244dc15 100644 (file)
@@ -39,7 +39,6 @@ abstract class Fetch
                $this->args = $args;
                $this->url = new Url("https://api.github.com/", null, 0);
                if (isset($this->config->fetch->{$this}->per_page)) {
-                       header("X-Fetch-{$this}: ".$this->config->fetch->{$this}->per_page, false);
                        $this->url->query = "per_page=" . $this->config->fetch->{$this}->per_page;
                }
        }
diff --git a/app/Model/Account.php b/app/Model/Account.php
new file mode 100644 (file)
index 0000000..dec3877
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Row;
+
+class Account extends Row
+{
+       function updateToken($authority, $token, $json) {
+               $tokens = $this->table->getTokens();
+               
+               $existing = $tokens->find([
+                       "authority=" => $authority,
+                       "account=" => $this->account,
+               ]);
+               
+               if (count($existing)) {
+                       $found = $existing->current();
+                       $found->token = $token;
+                       $found->oauth = $json;
+                       $found->update();
+                       return $found;
+               }
+               
+               return $tokens->create([
+                       "authority" => $authority,
+                       "account" => $this->account,
+                       "token" => $token,
+                       "oauth" => $json
+               ])->current();
+       }
+       
+       function updateOwner($authority, $user, $json) {
+               $owners = $this->table->getOwners();
+               
+               $existing = $owners->find([
+                       "authority=" => $authority,
+                       "account=" => $this->account,
+               ]);
+               
+               if (count($existing)) {
+                       $found = $existing->current();
+                       $found->login = $user;
+                       $found->owner = $json;
+                       $found->update();
+                       return $found;
+               }
+               
+               return $owners->create([
+                       "authority" => $authority,
+                       "login" => $user,
+                       "owner" => $json
+               ])->current();
+       }
+}
diff --git a/app/Model/AccountCollection.php b/app/Model/AccountCollection.php
new file mode 100644 (file)
index 0000000..d1354b7
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Rowset;
+
+class AccountCollection extends Rowset
+{
+       protected $row = "app\\Model\\Account";
+}
diff --git a/app/Model/Accounts.php b/app/Model/Accounts.php
new file mode 100644 (file)
index 0000000..11f5156
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+namespace app\Model;
+
+use pq\Connection;
+use pq\Gateway\Table;
+use pq\Query\Expr;
+
+class Accounts extends Table
+{
+       protected $rowset = "app\\Model\\AccountCollection";
+       
+       /**
+        * @var \app\Model\Tokens
+        */
+       private $tokens;
+       
+       /**
+        * @var \app\Model\Owners
+        */
+       private $owners;
+       
+       function __construct(Connection $conn, Tokens $tokens, Owners $owners) {
+               parent::__construct("accounts", $conn);
+               $this->tokens = $tokens;
+               $this->owners = $owners;
+       }
+       
+       function getTokens() {
+               return $this->tokens;
+       }
+       
+       function getOwners() {
+               return $this->owners;
+       }
+       
+       function byOAuth($authority, $token, $user) {
+               if (!($account = $this->byOAuthToken($authority, $token))) {
+                       $account = $this->byOAuthOwner($authority, $user);
+               }
+               return $account;
+       }
+       
+       function byOAuthToken($authority, $access_token, &$token = null) {
+               $tokens = $this->tokens->find([
+                       "token=" => $access_token,
+                       "authority=" => $authority,
+               ]);
+               
+               if (count($tokens)) {
+                       $token = $tokens->current();
+                       $accounts = $this->by($token);
+                       if (count($accounts)) {
+                               return $accounts->current();
+                       }
+               }
+       }
+       
+       function byOAuthOwner($authority, $login, &$owner = null) {
+               $owners = $this->owners->find([
+                       "authority=" => $authority,
+                       "login=" => $login,
+               ]);
+               
+               if (count($owners)) {
+                       $owner = $owners->current();
+                       $accounts = $this->by($owner);
+                       if (count($accounts)) {
+                               return $accounts->current();
+                       }
+               }
+       }
+       
+       function createOAuthAccount($authority, $token, $user) {
+               $account = $this->create([
+                       "account" => new Expr("DEFAULT")
+               ])->current();
+               $this->owners->create([
+                       "account" => $account->account,
+                       "authority" => $authority,
+                       "login" => $user,
+               ]);
+               $this->tokens->create([
+                       "account" => $account->account,
+                       "authority" => "github",
+                       "token" => $token
+               ]);
+               return $account;
+       }
+}
diff --git a/app/Model/Authorities.php b/app/Model/Authorities.php
new file mode 100644 (file)
index 0000000..23cfb67
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Table;
+
+class Authorities extends Table
+{
+       protected $name = "authorities";
+       protected $rowset = "app\\Model\\AuthorityCollection";
+}
diff --git a/app/Model/Authority.php b/app/Model/Authority.php
new file mode 100644 (file)
index 0000000..58ce74c
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Row;
+
+class Authority extends Row
+{
+}
diff --git a/app/Model/AuthorityCollection.php b/app/Model/AuthorityCollection.php
new file mode 100644 (file)
index 0000000..3d0209a
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Rowset;
+
+class AuthorityCollection extends Rowset
+{
+       protected $row = "app\\Model\\Authority";
+}
diff --git a/app/Model/Owner.php b/app/Model/Owner.php
new file mode 100644 (file)
index 0000000..386425d
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Row;
+
+class Owner extends Row
+{
+}
diff --git a/app/Model/OwnerCollection.php b/app/Model/OwnerCollection.php
new file mode 100644 (file)
index 0000000..4fed5d5
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Rowset;
+
+class OwnerCollection extends Rowset
+{
+       protected $row = "app\\Model\\Owner";
+}
diff --git a/app/Model/Owners.php b/app/Model/Owners.php
new file mode 100644 (file)
index 0000000..949a1d4
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Table;
+
+class Owners extends Table
+{
+       protected $name = "owners";
+       protected $rowset = "app\\Model\\OwnerCollection";
+}
diff --git a/app/Model/Token.php b/app/Model/Token.php
new file mode 100644 (file)
index 0000000..3d6052a
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Row;
+
+class Token extends Row
+{
+}
diff --git a/app/Model/TokenCollection.php b/app/Model/TokenCollection.php
new file mode 100644 (file)
index 0000000..72af3e2
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Rowset;
+
+class TokenCollection extends Rowset
+{
+       protected $row = "app\\Model\\Token";
+}
diff --git a/app/Model/Tokens.php b/app/Model/Tokens.php
new file mode 100644 (file)
index 0000000..15259d2
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+namespace app\Model;
+
+use pq\Gateway\Table;
+
+class Tokens extends Table
+{
+       protected $name = "tokens";
+       protected $rowset = "app\\Model\\TokenCollection";
+}
index 87189dc0bacda2584fe589573467cf19c4ce042d..f2a5dbd58ac52de334d1f1eb3f95b921cc93244c 100644 (file)
@@ -3,14 +3,25 @@
 namespace app;
 
 use ArrayAccess;
-use merry\Config;
+use http\Env\Response;
+use http\Params;
 
 class Session implements ArrayAccess
 {
-       function __construct(Config $config) {
+       function __construct(Config $config, Response $response) {
                foreach ($config->session as $key => $val) {
                        ini_set("session.$key", $val);
                }
+               if (ini_get("session.use_cookies")) {
+                       $response->addHeader("Vary", "cookie");
+               }
+               $response->addHeader("Cache-Control",
+                       new Params([
+                               "private" => true,
+                               "must-revalidate" => true,
+                               "max-age" => ini_get("session.cache_expire") * 60
+                       ])
+               );
                session_start();
        }
 
index 4a0239e910d050d5663835aa8cdfeb83aa65c36a..8142fb11141b97e0fe94160fb9a433005885ef34 100644 (file)
@@ -15,6 +15,8 @@ $injector->share(Args::class)
        ->define(Args::class, [
                ":spec" => [
                        [null, "ngrok", "Run ngrok", Args::SINGLE],
+                       [null, "initdb", "Create database", Args::SINGLE],
+                       [null, "gen-models", "Generate pq\\Gateway models", Args::SINGLE],
                        ["h", "help", "Show this help", Args::HALT],
                ]
        ]);
index cbf70d2d80bc843353af6b0b9be05585c6af6671..3bc7f910667144c4c91f37c9d140cc57e5d05376 100644 (file)
@@ -2,25 +2,16 @@
 
 namespace app;
 
-use merry\Config;
-
 define("APP_ENVIRONMENT", getenv("APP_ENVIRONMENT") ?: "localhost");
 
 $injector->share(Config::class)
        ->define(Config::class, [
-               ":array" => parse_ini_file(__DIR__."/../config.ini", true),
+               "+array" => function($key, $injector) {
+                       return parse_ini_file(__DIR__."/../../config/app.ini", true);
+               },
                ":section" => APP_ENVIRONMENT
        ])
-       ->prepare(Config::class, function(Config $config) {
-               $credentials = parse_ini_file(__DIR__."/../credentials.ini", true);
-               foreach (new Config($credentials, APP_ENVIRONMENT) as $app => $creds) {
-                       /* one level down should suffice, i.e. $config->github->client = {id,secret,scope} */
-                       if ($creds instanceof Config) {
-                               foreach ($creds as $key => $val) {
-                                       $config->$app->$key = $val;
-                               }
-                       } else {
-                               $config->$app = $creds;
-                       }
-               }
+       ->prepare(Config::class, function($config, $injector) {
+               $credentials = parse_ini_file(__DIR__."/../../config/credentials.ini", true);
+               $config->addConfig(new Config($credentials, APP_ENVIRONMENT));
        });
index fe3477f4d027ea6c607e9fdd150fc50d18ea2bcd..04396fb6cfaf30a1b849439c269150e68a5099ee 100644 (file)
@@ -4,7 +4,6 @@ namespace app;
 
 require_once __DIR__."/config.php";
 
-use merry\Config;
 use http\Url;
 
 $injector->share(Github\API::class)
diff --git a/app/bootstrap/http.php b/app/bootstrap/http.php
new file mode 100644 (file)
index 0000000..46fc75f
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+namespace app;
+
+use http\Env\Request;
+use http\Env\Response;
+
+$injector->share(Request::class);
+$injector->share(Response::class);
diff --git a/app/bootstrap/model.php b/app/bootstrap/model.php
new file mode 100644 (file)
index 0000000..105125e
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace app;
+
+require_once __DIR__."/config.php";
+require_once __DIR__."/pq.php";
+
+use pq\Connection;
+
+/* @var $injector \Auryn\Injector */
+
+$injector->define(Model\Accounts::class, [
+               "conn" => Connection::class,
+       ])
+       ->define(Model\Tokens::class, [
+               "conn" => Connection::class,
+       ])
+       ->define(Model\Authorities::class, [
+               "conn" => Connection::class,
+       ])
+       ->define(Model\Owners::class, [
+               "conn" => Connection::class,
+       ]);
+
+//$modelconf = function($key, $injector) {
+//     return new Table($key, $injector->make(Connection::class));
+//};
+//
+//$injector->define(Model\Account::class, [
+//     "+accounts" => $modelconf,
+//     "+owners" => $modelconf,
+//     "+tokens" => $modelconf
+//]);
index 0b14222e98346031fd5b783abfff52c71ff2f976..f31d394847bffc55394c5a83efb53616ec43aa65 100644 (file)
@@ -10,8 +10,6 @@ use League\Plates;
 use http\Env\Request;
 use http\Env\Response;
 
-use merry\Config;
-
 $injector->share(Plates\Engine::class)
        ->define(Plates\Engine::class, [
                ":directory" => __DIR__."/../views",
diff --git a/app/bootstrap/pq.php b/app/bootstrap/pq.php
new file mode 100644 (file)
index 0000000..0aa3027
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+namespace app;
+
+require_once __DIR__."/config.php";
+
+use pq\Connection;
+
+/* @var $injector \Auryn\Injector */
+
+$pqconfig = function($key, $injector) {
+       return $injector->make(Config::class)->pq->$key;
+};
+
+$injector->share(Connection::class)
+       ->define(Connection::class, [
+               "+dsn" => $pqconfig,
+               "+flags" => $pqconfig
+       ]);
diff --git a/app/bootstrap/router.php b/app/bootstrap/router.php
new file mode 100644 (file)
index 0000000..7377cfd
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+namespace app;
+
+use Auryn\Injector;
+
+use FastRoute\DataGenerator;
+use FastRoute\Dispatcher;
+use FastRoute\RouteCollector;
+use FastRoute\RouteParser;
+
+use http\Env\Response;
+
+$injector->share(RouteCollector::class)
+       ->prepare(RouteCollector::class, function($routes) use($injector) {
+               $routes->addRoute("GET", "/reset", function(array $args = null) use($injector) {
+                       $injector->make(Session::class)->reset()->regenerateId();
+                       $injector->make(Web::class)->redirect($injector->make(BaseUrl::class));
+               });
+               $routes->addRoute("GET", "/session", function(array $args = null) use($injector) {
+                       $session = $injector->make(Session::class);
+                       $response = $injector->make(Response::class);
+                       if (!(extension_loaded("xdebug") && ini_get("xdebug.overload_var_dump") && ini_get("html_errors"))) {
+                               $response->setContentType("text/plain");
+                       }
+                       ob_start($response);
+                       var_dump($_SESSION, $session);
+               });
+               $routes->addRoute("GET", "/info", function(array $args = null) {
+                       phpinfo();
+               });
+
+               foreach (parse_ini_file(__DIR__."/../../config/routes.ini", true) as $controller => $definition) {
+                       $factory = function(array $args = null) use($injector, $controller) {
+                               $handler = $injector->make("app\\Controller\\$controller");
+                               $handler($args);
+                       };
+                       foreach ($definition as $method => $locations) {
+                               foreach ($locations as $location) {
+                                       $routes->addRoute($method, $location, $factory);
+                               }
+                       }
+               }
+       })
+       ->alias(RouteParser::class, RouteParser\Std::class)
+       ->alias(DataGenerator::class, DataGenerator\GroupCountBased::class);
+
+$injector->share(Dispatcher::class)
+       ->alias(Dispatcher::class, Dispatcher\GroupCountBased::class)
+       ->delegate(Dispatcher\GroupCountBased::class, function($class, Injector $injector) {
+               return new $class($injector->make(RouteCollector::class)->getData());
+       });
+
diff --git a/app/bootstrap/session.php b/app/bootstrap/session.php
new file mode 100644 (file)
index 0000000..55e6b2a
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+namespace app;
+
+require_once __DIR__."/http.php";
+
+use Auryn\Injector;
+use http\Env\Request;
+
+$injector->share(Session::class)
+       ->prepare(Session::class, function(Session $session, Injector $injector) {
+               if (isset($session->current) && (!isset($session->previous) || strcmp($session->current, $session->previous))) {
+                       $session->previous = $session->current;
+                       $session->current = $injector->make(Request::class)->getRequestUrl();
+               }
+               $session->current = $injector->make(Request::class)->getRequestUrl();
+       });
index 755bb79acfef14af7d2c119014f4fb8a4a88d260..12cefeb2f5d3374f5570556a9e6fa1f337fe5f30 100644 (file)
@@ -3,75 +3,21 @@
 namespace app;
 
 require_once __DIR__."/config.php";
+require_once __DIR__."/http.php";
 require_once __DIR__."/github.php";
+require_once __DIR__."/router.php";
+require_once __DIR__."/session.php";
 
 use Auryn\Injector;
 
-use FastRoute\DataGenerator;
-use FastRoute\Dispatcher;
-use FastRoute\RouteCollector;
-use FastRoute\RouteParser;
-
-use http\Env\Request;
-use http\Env\Response;
-
-$injector->share(Request::class);
-$injector->share(Response::class);
-
-$injector->share(RouteCollector::class)
-       ->prepare(RouteCollector::class, function($routes) use($injector) {
-               $routes->addRoute("GET", "/reset", function(array $args = null) use($injector) {
-                       $injector->make(Session::class)->reset()->regenerateId();
-                       $injector->make(Web::class)->redirect($injector->make(BaseUrl::class));
-               });
-               $routes->addRoute("GET", "/session", function(array $args = null) use($injector) {
-                       $session = $injector->make(Session::class);
-                       $response = $injector->make(Response::class);
-                       if (!(extension_loaded("xdebug") && ini_get("xdebug.overload_var_dump") && ini_get("html_errors"))) {
-                               $response->setContentType("text/plain");
-                       }
-                       ob_start($response);
-                       var_dump($_SESSION, $session);
-               });
-               $routes->addRoute("GET", "/info", function(array $args = null) {
-                       phpinfo();
-               });
-
-               foreach (parse_ini_file(__DIR__."/../routes.ini", true) as $controller => $definition) {
-                       $factory = function(array $args = null) use($injector, $controller) {
-                               $handler = $injector->make("app\\Controller\\$controller");
-                               $handler($args);
-                       };
-                       foreach ($definition as $method => $locations) {
-                               foreach ($locations as $location) {
-                                       $routes->addRoute($method, $location, $factory);
-                               }
-                       }
-               }
-       })
-       ->alias(RouteParser::class, RouteParser\Std::class)
-       ->alias(DataGenerator::class, DataGenerator\GroupCountBased::class);
-
-$injector->share(Dispatcher::class)
-       ->alias(Dispatcher::class, Dispatcher\GroupCountBased::class)
-       ->delegate(Dispatcher\GroupCountBased::class, function($class, Injector $injector) {
-               return new $class($injector->make(RouteCollector::class)->getData());
-       });
-
 $injector->prepare(Controller::class, function(Controller $controller, Injector $injector) {
        if (method_exists($controller, "setSession")) {
                $controller->setSession($injector->make(Session::class));
        }
+       if (method_exists($controller, "setDatabase")) {
+               $controller->setDatabase($injector->make(Connection::class));
+       }
 });
 
-$injector->share(Session::class)
-       ->prepare(Session::class, function(Session $session, Injector $injector) {
-               if (isset($session->current) && (!isset($session->previous) || strcmp($session->current, $session->previous))) {
-                       $session->previous = $session->current;
-                       $session->current = $injector->make(Request::class)->getRequestUrl();
-               }
-               $session->current = $injector->make(Request::class)->getRequestUrl();
-       });
-
 $injector->share(BaseUrl::class);
 $injector->share(Web::class);
diff --git a/app/config.ini b/app/config.ini
deleted file mode 100644 (file)
index 66d0376..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-[production]
-
-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
-github.storage.cache.hooks.ttl = 3600
-github.storage.cache.tags.ttl = 3600
-github.storage.cache.releases.ttl = 3600
-
-session.use_cookies = 1
-session.use_only_cookies = 1
-session.use_strict_mode = 1
-session.cookie_httponly = 1
-session.cookie_secure = 1
-session.cache_limiter = private_no_expire
-; minutes
-session.cache_expire = 0
-
-[localhost : production]
-
-github.hook.url = https://pharext.ngrok.io/src/pharext.org.git/public/github/hook
-github.hook.insecure_ssl = 1
-github.hook.use_basic_auth = ngrok
-
-ngrok.command = ngrok
\ No newline at end of file
diff --git a/app/routes.ini b/app/routes.ini
deleted file mode 100644 (file)
index ad01201..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-[Homepage]
-GET[] = /
-
-[Wikipage]
-GET[] = /pages/{page}
-
-[Github\Callback]
-GET[] = /github/callback
-
-[Github\Hook]
-POST[] = /github/hook
-
-[Github\Index]
-GET[] = /github
-
-[Github\Repo]
-GET[] = /github/repo/{owner}/{name}
-GET[] = /github/repo/{owner}/{name}/{page}
-
-[Github\Repo\Hook]
-POST[] = /github/repo/{owner}/{name}/hook/{action}
-; if the user has to re-authenticate, we'll receive a GET because of a redirect
-GET[] = /github/repo/{owner}/{name}/hook/{action}
-
-[Github\Signin]
-GET[] = /github/signin
-
index 372de5d5a6c65234e151185c037060064415b700..36849c00094ae9720b192637cb4cfe5ac392ccdf 100644 (file)
        </div>
 
        <div class="col-md-6">
-               
-               <div class="row text-center">
-                       <div class="col-md-6">
-                               <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="row">
+                       <form method="post" action="<?= $baseUrl->mod("./github/repo/". $repo->full_name ."/hook/" . ($this->check($repo) ? "upd" : "add")) ?>">
+                               <div class="col-md-12">
+                                               <div class="checkbox">
+                                                       <label for="hook-tag">
+                                                               <input id="hook-tag" type="checkbox" name="tag" value="1">
+                                                               Automatically create a release when I push a tag.
+                                                       </label>
+                                               </div>
+                                               <div class="checkbox">
+                                                       <label for="hook-release">
+                                                               <input id="hook-release" type="checkbox" name="release" value="1">
+                                                               Automatically upload a PHARext package as an asset to a release.
+                                                       </label>
+                                               </div>
+                               </div>
+                               <?php if ($this->check($repo)) : ?>
+                               <div class="col-md-6">
+                                       <button type="submit" class="btn btn-lg btn-block btn-info">
+                                               <span class="glyphicon glyphicon-ok-circle"></span>
+                                               Update Hook
+                                       </button>
+                               </div>
+                               <?php else : ?>
+                               <div class="col-md-12">
+                                       <button type="submit" class="btn btn-lg btn-block btn-success">
+                                               <span class="octicon octicon-plug"></span>
+                                               Enable Hook
+                                       </button>
+                               </div>
+                               <?php endif; ?>
+                       </form>
+                       <!-- column wrapping! -->
+                       <?php if ($this->check($repo)) : ?>
                        <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>
+                               <button class="btn btn-lg btn-block btn-danger">
+                                       <span class="glyphicon glyphicon-remove-circle"></span>
+                                       Remove Hook
+                               </button>
                                </form>
                        </div>
+                       <?php endif; ?>
                </div>
-               </form>
        </div>
 </div>
 
index 1d14a0bdf37e6d1ff8814a2ceac62af071229540..c7fbfb5f3a961ba58c62b88d672275a61d8f5c95 100644 (file)
                                <ul class="nav navbar-nav navbar-right">
                                        <li class="dropdown">
                                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
-                                                       <img src="<?= $session->github->avatar_url ?>" 
+                                                       <img src="<?= $session->github->owner["avatar_url"] ?>" 
                                                         class="img-circle"
                                                         alt="avatar"
                                                         width="32"
                                                         style="margin:-15px 0px">
-                                                       <abbr title="<?= $this->e($session->github->name) ?>">
-                                                               <?= $this->e($session->github->login) ?>
+                                                       <abbr title="<?= $this->e($session->github->owner["name"]) ?>">
+                                                               <?= $this->e($session->github->owner["login"]) ?>
                                                        </abbr>
                                                        <span class="caret"></span>
                                                </a>
index 89375685a0645a70a8d51901753652ff5ba335ae..43ba6c4a7c0c037b2ca94bb024baadbc8a0d722c 100755 (executable)
@@ -4,5 +4,7 @@
 namespace app;
 
 $bootstrap = require __DIR__."/../app/bootstrap.php";
-$injector = $bootstrap(["config", "github", "cli"]);
-$injector->execute(Cli::class, [$argc, $argv]);
+$injector = $bootstrap(["config", "github", "pq", "cli"]);
+$injector->execute(Cli::class, [$argc, $argv, function($command, array $args = []) use($injector) {
+       $injector->execute($injector->make($command), $args);
+}]);
index c8914d0f84663341c1142a9da726b2d62d72406e..685df297c615f9772c6fa3164edd3519897742c6 100644 (file)
                }
        },
        "require": {
-               "m6w6/pharext": "~3.0",
+               "m6w6/pharext": "dev-master",
                "m6w6/merry": "~1.1",
                "nikic/fast-route": "0.4.*",
                "league/plates": "~3.1",
                "rdlowrey/auryn": "dev-master",
-               "m6w6/pharext.wiki": "dev-master"
+               "m6w6/pharext.wiki": "dev-master",
+               "m6w6/pq-gateway": "^2.0"
        }
 }
index 9ed2b4dd48097bc8303c1ed415825007feeed279..b6233cb05ec7e951445325577d894d624ffb875e 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "c13d01568dea02e2afe4b9060478178f",
+    "hash": "a2c6e7fb3b26571277d242842331c3cb",
     "packages": [
         {
             "name": "league/plates",
         },
         {
             "name": "m6w6/pharext",
-            "version": "v3.0.1",
+            "version": "dev-master",
             "source": {
                 "type": "git",
                 "url": "https://github.com/m6w6/pharext.git",
-                "reference": "3dcc326aa922ce4b70d07049572c6f15a744869c"
+                "reference": "e458434f1a5539f1905e61835bed0123f1635067"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/m6w6/pharext/zipball/3dcc326aa922ce4b70d07049572c6f15a744869c",
-                "reference": "3dcc326aa922ce4b70d07049572c6f15a744869c",
+                "url": "https://api.github.com/repos/m6w6/pharext/zipball/e458434f1a5539f1905e61835bed0123f1635067",
+                "reference": "e458434f1a5539f1905e61835bed0123f1635067",
                 "shasum": ""
             },
             "bin": [
                 "package",
                 "phar"
             ],
-            "time": "2015-04-08 10:17:40"
+            "time": "2015-05-08 08:54:09"
         },
         {
             "name": "m6w6/pharext.wiki",
             "type": "library",
             "time": "2015-04-30 17:39:40"
         },
+        {
+            "name": "m6w6/pq-gateway",
+            "version": "v2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/m6w6/pq-gateway.git",
+                "reference": "19590cfee428909a9ad2195b02a30ae176980992"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/m6w6/pq-gateway/zipball/19590cfee428909a9ad2195b02a30ae176980992",
+                "reference": "19590cfee428909a9ad2195b02a30ae176980992",
+                "shasum": ""
+            },
+            "suggest": {
+                "react/promise": "1.0.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "pq\\Gateway": "lib",
+                    "pq\\Query": "lib"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Wallner",
+                    "email": "mike@php.net"
+                }
+            ],
+            "description": "Table/row gateway for ext-pq",
+            "homepage": "http://github.com/m6w6/pq-gateway",
+            "keywords": [
+                "gateway",
+                "orm",
+                "postgres",
+                "postgresql",
+                "pq"
+            ],
+            "time": "2014-10-15 15:30:26"
+        },
         {
             "name": "nikic/fast-route",
             "version": "v0.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/rdlowrey/Auryn.git",
-                "reference": "500b08ec9942eb8b647b128fa13942d45754e3d4"
+                "reference": "2e03bf93ddcd855e962d314cce74ce2a01a49228"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/rdlowrey/Auryn/zipball/500b08ec9942eb8b647b128fa13942d45754e3d4",
-                "reference": "500b08ec9942eb8b647b128fa13942d45754e3d4",
+                "url": "https://api.github.com/repos/rdlowrey/Auryn/zipball/be3807b62a6991e61151b55b6974dc1289afabd5",
+                "reference": "2e03bf93ddcd855e962d314cce74ce2a01a49228",
                 "shasum": ""
             },
             "require": {
                 "dic",
                 "ioc"
             ],
-            "time": "2015-03-12 19:52:27"
+            "time": "2015-05-01 14:20:14"
         }
     ],
     "packages-dev": [],
     "aliases": [],
     "minimum-stability": "stable",
     "stability-flags": {
+        "m6w6/pharext": 20,
         "rdlowrey/auryn": 20,
         "m6w6/pharext.wiki": 20
     },
diff --git a/config/app.ini b/config/app.ini
new file mode 100644 (file)
index 0000000..1cdc655
--- /dev/null
@@ -0,0 +1,36 @@
+[production]
+
+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
+github.storage.cache.hooks.ttl = 3600
+github.storage.cache.tags.ttl = 3600
+github.storage.cache.releases.ttl = 3600
+
+session.use_cookies = 1
+session.use_only_cookies = 1
+session.use_strict_mode = 1
+session.cookie_httponly = 1
+session.cookie_secure = 1
+session.cache_limiter = ""
+;private_no_expire
+; minutes
+session.cache_expire = 0
+
+pq.flags = 0
+pq.dsn = "user=pharext host=localhost"
+
+[localhost : production]
+
+github.hook.url = https://pharext.ngrok.io/src/pharext.org.git/public/github/hook
+github.hook.insecure_ssl = 1
+github.hook.use_basic_auth = ngrok
+
+ngrok.command = ngrok
\ No newline at end of file
diff --git a/config/routes.ini b/config/routes.ini
new file mode 100644 (file)
index 0000000..ad01201
--- /dev/null
@@ -0,0 +1,27 @@
+[Homepage]
+GET[] = /
+
+[Wikipage]
+GET[] = /pages/{page}
+
+[Github\Callback]
+GET[] = /github/callback
+
+[Github\Hook]
+POST[] = /github/hook
+
+[Github\Index]
+GET[] = /github
+
+[Github\Repo]
+GET[] = /github/repo/{owner}/{name}
+GET[] = /github/repo/{owner}/{name}/{page}
+
+[Github\Repo\Hook]
+POST[] = /github/repo/{owner}/{name}/hook/{action}
+; if the user has to re-authenticate, we'll receive a GET because of a redirect
+GET[] = /github/repo/{owner}/{name}/hook/{action}
+
+[Github\Signin]
+GET[] = /github/signin
+
diff --git a/config/sql/001.sql b/config/sql/001.sql
new file mode 100644 (file)
index 0000000..241b38d
--- /dev/null
@@ -0,0 +1,30 @@
+drop table if exists authorities cascade;
+drop table if exists accounts cascade;
+drop table if exists tokens cascade;
+drop table if exists owners cascade;
+
+create table authorities (
+        authority text not null primary key
+);
+
+insert into authorities values('github');
+
+create table accounts (
+        account uuid not null default uuid_generate_v4() primary key
+);
+
+create table tokens (
+        token text not null primary key
+       ,account uuid not null references accounts on update cascade on delete cascade
+       ,authority text not null references authorities on update cascade on delete cascade
+       ,oauth jsonb
+);
+
+create table owners (
+        account uuid not null references accounts on update cascade on delete cascade
+       ,authority text not null references authorities on update cascade on delete cascade
+       ,login text not null
+       ,owner jsonb
+       ,primary key (account,authority)
+       ,unique key (login,authority)
+);
index 8d0bf63970aeb79d4f3effa70f76893badc0e84e..5718129b2397f3b9e978b8511b630421b8083919 100644 (file)
@@ -3,5 +3,5 @@
 namespace app;
 
 $bootstrap = require "../app/bootstrap.php";
-$injector = $bootstrap(["config", "github", "plates", "web"]);
+$injector = $bootstrap(["config", "github", "plates", "model", "web"]);
 $injector->execute(Web::class);