Issue and PR webhooks

This commit is contained in:
kagel 2016-08-28 23:16:45 +03:00
parent a6459a2a32
commit 4a5196987b
16 changed files with 254 additions and 67 deletions

View File

@ -1,3 +1,4 @@
DROP TABLE users;
DROP TABLE repositories;
DROP TABLE issues;
DROP TABLE pull_requests;

View File

@ -1,31 +1,38 @@
CREATE TABLE users (
id VARCHAR(40) PRIMARY KEY, -- user id
login VARCHAR(64) UNIQUE NOT NULL, -- github login
name VARCHAR(128), -- user name
email VARCHAR(128), -- user email, if present
token VARCHAR(40) NOT NULL, -- github oauth token
address VARCHAR(42), -- ETH address
created TIME -- user created date
CREATE TABLE users
(
id INTEGER PRIMARY KEY NOT NULL,
login VARCHAR(64) NOT NULL,
name VARCHAR(128),
email VARCHAR(128),
token VARCHAR(40),
address VARCHAR(42),
created TIME
);
CREATE UNIQUE INDEX users_login_key ON users (login);
CREATE TABLE repositories
(
login VARCHAR(64) NOT NULL, -- github user
repo VARCHAR(64) NOT NULL, -- github repo
updated TIME, -- date of the last crawl
repo_id INTEGER NOT NULL, -- github repository id
enabled BOOLEAN DEFAULT TRUE
repo_id INTEGER PRIMARY KEY NOT NULL,
user_id INTEGER,
login VARCHAR(64) NOT NULL,
repo VARCHAR(64) NOT NULL,
updated TIME,
enabled BOOLEAN DEFAULT TRUE,
hook_id INTEGER
);
CREATE UNIQUE INDEX repositories_user_repo_pk ON repositories (login, repo);
CREATE UNIQUE INDEX repositories_repo_id_pk ON repositories (repo_id);
CREATE INDEX repositories_login_repo_index ON repositories (login, repo);
CREATE INDEX repositories_repo_id_index ON repositories (repo_id);
CREATE TABLE issues
(
repo_id INTEGER NOT NULL,
issue_id INTEGER NOT NULL,
address VARCHAR(42),
CONSTRAINT issues_repo_id_issue_id_pk PRIMARY KEY (repo_id, issue_id)
issue_id INTEGER PRIMARY KEY NOT NULL,
repo_id INTEGER NOT NULL,
address VARCHAR(256),
issue_number INTEGER,
commit_id VARCHAR(40)
);
CREATE TABLE pull_requests
(
pr_id INTEGER PRIMARY KEY NOT NULL,
repo_id INTEGER,
user_id INTEGER,
parents VARCHAR(4099) -- 100 commit SHAs + 99 commas
);

View File

@ -4,7 +4,17 @@
-- :doc creates a new user record
INSERT INTO users
(id, login, name, email, token, address, created)
VALUES (:id, :login, :name, :email, :token, :address, :created);
SELECT
:id,
:login,
:name,
:email,
:token,
:address,
:created
WHERE NOT exists(SELECT 1
FROM users
WHERE id = :id);
-- :name update-user! :! :n
-- :doc updates an existing user record
@ -16,19 +26,19 @@ WHERE id = :id;
-- :doc updates user token and returns updated user
UPDATE users
SET token = :token
WHERE login = :login
WHERE id = :id
RETURNING id, login, name, email, token, address, created;
-- :name update-user-address! :! :n
UPDATE users
SET address = :address
WHERE login = :login;
WHERE id = :id;
-- :name get-user :? :1
-- :doc retrieve a user given the login.
SELECT *
FROM users
WHERE login = :login;
WHERE id = :id;
-- Repositories --------------------------------------------------------------------
@ -56,10 +66,68 @@ RETURNING repo_id, login, repo, enabled;
-- :doc returns enabled repositories for a given login
SELECT repo_id
FROM repositories
WHERE login = :login AND enabled = TRUE;
WHERE user_id = :user_id AND enabled = TRUE;
-- :name update-hook-id :! :n
-- :doc updates hook_id of a specified repository
UPDATE repositories
SET hook_id = :hook_id
WHERE repo_id = :repo_id;
-- Issues --------------------------------------------------------------------------
-- :name create-issue! :! :n
-- :doc creates issue
INSERT INTO issues (repo_id, issue_id, issue_number, address)
SELECT
:repo_id,
:issue_id,
:issue_number,
:address
WHERE NOT exists(SELECT 1
FROM issues
WHERE repo_id = :repo_id AND issue_id = :issue_id);
-- :name close-issue! :<! :1
-- :doc updates issue with commit id
UPDATE issues
SET commit_id = :commit_id
WHERE issue_id = :issue_id
RETURNING repo_id, issue_id, issue_number, address, commit_id;
-- Pull Requests -------------------------------------------------------------------
-- :name create-pull-request! :! :n
-- :doc creates pull request
INSERT INTO pull_requests (repo_id, pr_id, user_id, parents)
SELECT
:repo_id,
:pr_id,
:user_id,
:parents
WHERE NOT exists(SELECT 1
FROM pull_requests
WHERE repo_id = :repo_id AND pr_id = :pr_id);
-- Bounties ------------------------------------------------------------------------
-- :name bounties-list :? :*
-- :doc lists fixed issues
SELECT
i.address AS issue_address,
i.repo_id AS repo_id,
p.pr_id AS pr_id,
p.user_id AS user_id,
u.address AS payout_address,
u.login AS user_login,
u.name AS user_name,
r.repo AS repo_name
FROM issues i
INNER JOIN pull_requests p
ON p.parents LIKE '%' || i.commit_id || '%'
AND p.repo_id = i.repo_id
INNER JOIN users u
ON u.id = p.user_id
INNER JOIN repositories r
ON r.repo_id = i.repo_id
WHERE r.user_id = :owner_id;

View File

@ -22,11 +22,12 @@
var context = "{{servlet-context}}";
var csrfToken = "{{csrf-token}}";
var authorizeUrl = "{{authorize-url}}";
var token = "{{token}}";
var userId = "{{userId}}";
var user = "{{login}}";
if (user === "") {
user = null;
}
var token = "{{token}}";
</script>
{% script "/js/app.js" %}
</body>

View File

@ -0,0 +1,19 @@
(ns commiteth.db.issues
(:require [commiteth.db.core :refer [*db*] :as db]
[clojure.java.jdbc :as jdbc]
[clojure.set :refer [rename-keys]]))
(defn create
"Creates issue"
[repo-id issue-id issue-number address]
(jdbc/with-db-connection [con-db *db*]
(db/create-issue! con-db {:repo_id repo-id
:issue_id issue-id
:issue_number issue-number
:address address})))
(defn close
"Updates issue with commit_id"
[issue-id commit-id]
(jdbc/with-db-connection [con-db *db*]
(db/close-issue! con-db {:issue_id issue-id :commit_id commit-id})))

View File

@ -0,0 +1,10 @@
(ns commiteth.db.pull-requests
(:require [commiteth.db.core :refer [*db*] :as db]
[clojure.java.jdbc :as jdbc]
[clojure.set :refer [rename-keys]]))
(defn create
"Creates pull-request"
[pull-request]
(jdbc/with-db-connection [con-db *db*]
(db/create-pull-request! con-db pull-request)))

View File

@ -19,10 +19,10 @@
(defn get-enabled
"Lists enabled repositories ids for a given login"
[login]
[user-id]
(->>
(jdbc/with-db-connection [con-db *db*]
(db/get-enabled-repositories con-db {:login login}))
(db/get-enabled-repositories con-db {:user_id user-id}))
(mapcat vals)))
(defn update-hook-id

View File

@ -1,13 +1,13 @@
(ns commiteth.db.users
(:require [commiteth.db.core :refer [*db*] :as db]
[clojure.java.jdbc :as jdbc])
(:import [java.util Date UUID]))
(:import [java.util Date]))
(defn create-user
[login name email token]
[user-id login name email token]
(jdbc/with-db-connection [con-db *db*]
(db/create-user! con-db
{:id (str (UUID/randomUUID))
{:id user-id
:login login
:name name
:email email
@ -16,22 +16,22 @@
:created (new Date)})))
(defn get-user
[login]
[user-id]
(jdbc/with-db-connection [con-db *db*]
(db/get-user con-db {:login login})))
(db/get-user con-db {:id user-id})))
(defn exists?
[login]
[user-id]
(jdbc/with-db-connection [con-db *db*]
(some? (db/get-user con-db {:login login}))))
(some? (db/get-user con-db {:id user-id}))))
(defn update-user-address
[login address]
[user-id address]
(jdbc/with-db-connection [con-db *db*]
(db/update-user-address! con-db {:login login :address address})))
(db/update-user-address! con-db {:id user-id :address address})))
(defn update-user-token
"Updates user token and returns updated user"
[login token]
[user-id token]
(jdbc/with-db-connection [con-db *db*]
(db/update-user-token! con-db {:login login :token token})))
(db/update-user-token! con-db {:id user-id :token token})))

View File

@ -41,6 +41,9 @@
:client-id client-id
:client-token client-secret})
(defn- self-auth-params []
{:auth (str self ":" self-password)})
(def repo-fields
[:id
:name
@ -85,11 +88,17 @@
(defn remove-webhook
[token user repo hook-id]
(println "removing webhook")
(println token user repo hook-id)
(repos/delete-hook user repo hook-id (auth-params token)))
(defn post-comment
[user repo issue-id]
(issues/create-comment user repo issue-id
"a comment with an image link to the web service"
{:auth (str self ":" self-password)}))
"a comment with an image link to the web service" (self-auth-params)))
(defn get-commit
[user repo commit-id]
(repos/specific-commit user repo commit-id (self-auth-params)))
(defn get-issue-events
[user repo issue-id]
(issues/issue-events user repo issue-id (self-auth-params)))

View File

@ -4,8 +4,8 @@
[ring.util.http-response :as response]
[clojure.java.io :as io]))
(defn home-page [{login :login token :token}]
(layout/render "home.html" {:login login :token token}))
(defn home-page [{user-id :id login :login token :token}]
(layout/render "home.html" {:userId user-id :login login :token token}))
(defroutes home-routes
(GET "/" {{identity :identity} :session}

View File

@ -13,12 +13,13 @@
(defn- get-or-create-user
[token]
(let [user (github/get-user token)
{email :email
name :name
login :login} user]
{email :email
name :name
login :login
user-id :id} user]
(or
(users/update-user-token login token)
(users/create-user login name email token))))
(users/update-user-token user-id token)
(users/create-user user-id login name email token))))
(defroutes redirect-routes
(GET "/callback" [code state]

View File

@ -34,16 +34,16 @@
(context "/api" []
(POST "/user/address" []
:auth-rules authenticated?
:body-params [user :- String, address :- String]
:body-params [user-id :- String, address :- String]
:summary "Update user address"
(let [result (users/update-user-address user address)]
(let [result (users/update-user-address user-id address)]
(if (= 1 result)
(ok)
(internal-server-error))))
(GET "/user" []
:auth-rules authenticated?
:current-user user
(ok {:user (users/get-user (:login user))}))
(ok {:user (users/get-user (:id user))}))
(GET "/user/repositories" []
:auth-rules authenticated?
:current-user user
@ -51,7 +51,7 @@
(GET "/repositories" []
:auth-rules authenticated?
:current-user user
(ok (repositories/get-enabled (:login user))))
(ok (repositories/get-enabled (:id user))))
(POST "/repository/toggle" {:keys [params]}
:auth-rules authenticated?
:current-user user

View File

@ -1,24 +1,95 @@
(ns commiteth.routes.webhooks
(:require [compojure.core :refer [defroutes POST]]
[commiteth.github.core :as github]
[ring.util.http-response :refer [ok]]))
[commiteth.db.pull-requests :as pull-requests]
[commiteth.db.issues :as issues]
[commiteth.db.users :as users]
[ring.util.http-response :refer [ok]]
[clojure.string :refer [join]])
(:import [java.util UUID]))
(def label-name "bounty")
(defn find-issue-closed-event
[events]
(first (filter #(= "closed" (:event %)) events)))
(defn handle-issue-closed
[{{{user :login} :owner repo :name} :repository
{issue-id :id issue-number :number} :issue}]
(future
(->>
(github/get-issue-events user repo issue-number)
(find-issue-closed-event)
(:commit_id)
(issues/close issue-id))))
(defn get-commit-parents
[commit]
(->> commit :parents (map :sha) (join ",")))
(defn handle-pull-request-closed
[{{{owner :login} :owner
repo-name :name
repo-id :id} :repository
{{user-id :id
login :login
name :name} :user
id :id
merge-commit-sha :merge_commit_sha} :pull_request}]
(future
(->>
(github/get-commit owner repo-name merge-commit-sha)
(get-commit-parents)
(hash-map :parents)
(merge {:repo_id repo-id
:pr_id id
:user_id user-id})
(pull-requests/create))
(users/create-user user-id login name nil nil)))
(defn labeled-as-bounty?
[action issue]
(and
(= "labeled" action)
(= label-name (get-in issue [:label :name]))))
(defn has-bounty-label?
[issue]
(let [labels (get-in issue [:issue :labels])]
(some #(= label-name (:name %)) labels)))
(defn gen-address []
(UUID/randomUUID))
(defn handle-issue
[issue]
(when-let [action (:action issue)]
(when (and
(= "labeled" action)
(= label-name (get-in issue [:label :name])))
(when (labeled-as-bounty? action issue)
(github/post-comment
(get-in issue [:repository :owner :login])
(get-in issue [:repository :name])
(get-in issue [:issue :number]))))
(get-in issue [:issue :number]))
(let [repo-id (get-in issue [:repository :id])
issue (:issue issue)
issue-id (:id issue)
issue-number (:number issue)]
(issues/create repo-id issue-id issue-number (gen-address))))
(when (and
(= "closed" action)
(has-bounty-label? issue))
(handle-issue-closed issue)))
(ok (str issue)))
(defn handle-pull-request
[pull-request]
(when (= "closed" (:action pull-request))
(handle-pull-request-closed pull-request))
(ok (str pull-request)))
(defroutes webhook-routes
(POST "/webhook" {:keys [params headers]}
(case (get headers "x-github-event")
"issues" (handle-issue params)
"pull_request" (handle-pull-request params)
(ok))))

View File

@ -85,7 +85,7 @@
(defn load-user []
(when-let [login js/user]
(rf/dispatch [:set-active-user {:login login :token js/token}])))
(rf/dispatch [:set-active-user {:login login :id js/userId :token js/token}])))
(defn init! []
(rf/dispatch-sync [:initialize-db])

View File

@ -84,9 +84,9 @@
(reg-event-fx
:save-user-address
(fn [{:keys [db]} [_ user address]]
(fn [{:keys [db]} [_ user-id address]]
{:db db
:http {:method POST
:url "/api/user/address"
:on-success #(println %)
:params {:user user :address address}}}))
:params {:user_id user-id :address address}}}))

View File

@ -5,13 +5,13 @@
[clojure.set :refer [rename-keys]]))
(defn save-address
[login address]
[user-id address]
(fn [_]
(rf/dispatch [:save-user-address login address])))
(rf/dispatch [:save-user-address user-id address])))
(defn address-settings []
(let [user (rf/subscribe [:user])
login (:login @user)
user-id (:id @user)
address (rf/subscribe [:get-in user-address-path])]
(fn []
[:div.form-group
@ -19,7 +19,7 @@
[input {:placeholder "Address"
:value-path user-address-path}]
[:button.btn.btn-primary.btn-lg
{:on-click (save-address login @address)}
{:on-click (save-address user-id @address)}
"Save"]])))
(defn repository-row [repo]