From 4a5196987bc54d8793432e9661f83791182d9131 Mon Sep 17 00:00:00 2001 From: kagel Date: Sun, 28 Aug 2016 23:16:45 +0300 Subject: [PATCH] Issue and PR webhooks --- .../migrations/20160820210822-init.down.sql | 1 + .../migrations/20160820210822-init.up.sql | 51 +++++++----- resources/sql/queries.sql | 78 ++++++++++++++++-- resources/templates/home.html | 3 +- src/clj/commiteth/db/issues.clj | 19 +++++ src/clj/commiteth/db/pull_requests.clj | 10 +++ src/clj/commiteth/db/repositories.clj | 4 +- src/clj/commiteth/db/users.clj | 22 ++--- src/clj/commiteth/github/core.clj | 15 +++- src/clj/commiteth/routes/home.clj | 4 +- src/clj/commiteth/routes/redirect.clj | 11 +-- src/clj/commiteth/routes/services.clj | 8 +- src/clj/commiteth/routes/webhooks.clj | 81 +++++++++++++++++-- src/cljs/commiteth/core.cljs | 2 +- src/cljs/commiteth/handlers.cljs | 4 +- src/cljs/commiteth/profile/page.cljs | 8 +- 16 files changed, 254 insertions(+), 67 deletions(-) create mode 100644 src/clj/commiteth/db/issues.clj create mode 100644 src/clj/commiteth/db/pull_requests.clj diff --git a/resources/migrations/20160820210822-init.down.sql b/resources/migrations/20160820210822-init.down.sql index 782efed..2c9becf 100644 --- a/resources/migrations/20160820210822-init.down.sql +++ b/resources/migrations/20160820210822-init.down.sql @@ -1,3 +1,4 @@ DROP TABLE users; DROP TABLE repositories; DROP TABLE issues; +DROP TABLE pull_requests; diff --git a/resources/migrations/20160820210822-init.up.sql b/resources/migrations/20160820210822-init.up.sql index 41ac26c..b3e9e83 100644 --- a/resources/migrations/20160820210822-init.up.sql +++ b/resources/migrations/20160820210822-init.up.sql @@ -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 ); diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index 07f4bf7..af19b7b 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -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! : {% script "/js/app.js" %} diff --git a/src/clj/commiteth/db/issues.clj b/src/clj/commiteth/db/issues.clj new file mode 100644 index 0000000..f42876d --- /dev/null +++ b/src/clj/commiteth/db/issues.clj @@ -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}))) diff --git a/src/clj/commiteth/db/pull_requests.clj b/src/clj/commiteth/db/pull_requests.clj new file mode 100644 index 0000000..3955f94 --- /dev/null +++ b/src/clj/commiteth/db/pull_requests.clj @@ -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))) diff --git a/src/clj/commiteth/db/repositories.clj b/src/clj/commiteth/db/repositories.clj index 8e0eda8..1f479f8 100644 --- a/src/clj/commiteth/db/repositories.clj +++ b/src/clj/commiteth/db/repositories.clj @@ -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 diff --git a/src/clj/commiteth/db/users.clj b/src/clj/commiteth/db/users.clj index ce393db..21c2432 100644 --- a/src/clj/commiteth/db/users.clj +++ b/src/clj/commiteth/db/users.clj @@ -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}))) diff --git a/src/clj/commiteth/github/core.clj b/src/clj/commiteth/github/core.clj index 40dc541..7a22f5e 100644 --- a/src/clj/commiteth/github/core.clj +++ b/src/clj/commiteth/github/core.clj @@ -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))) diff --git a/src/clj/commiteth/routes/home.clj b/src/clj/commiteth/routes/home.clj index 56a11fc..28cade6 100644 --- a/src/clj/commiteth/routes/home.clj +++ b/src/clj/commiteth/routes/home.clj @@ -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} diff --git a/src/clj/commiteth/routes/redirect.clj b/src/clj/commiteth/routes/redirect.clj index 830ba01..1d99f50 100644 --- a/src/clj/commiteth/routes/redirect.clj +++ b/src/clj/commiteth/routes/redirect.clj @@ -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] diff --git a/src/clj/commiteth/routes/services.clj b/src/clj/commiteth/routes/services.clj index ce21838..8b4acf0 100644 --- a/src/clj/commiteth/routes/services.clj +++ b/src/clj/commiteth/routes/services.clj @@ -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 diff --git a/src/clj/commiteth/routes/webhooks.clj b/src/clj/commiteth/routes/webhooks.clj index 543457f..e6143d7 100644 --- a/src/clj/commiteth/routes/webhooks.clj +++ b/src/clj/commiteth/routes/webhooks.clj @@ -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)))) diff --git a/src/cljs/commiteth/core.cljs b/src/cljs/commiteth/core.cljs index cf4542f..89dafa5 100644 --- a/src/cljs/commiteth/core.cljs +++ b/src/cljs/commiteth/core.cljs @@ -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]) diff --git a/src/cljs/commiteth/handlers.cljs b/src/cljs/commiteth/handlers.cljs index 6281585..c60068c 100644 --- a/src/cljs/commiteth/handlers.cljs +++ b/src/cljs/commiteth/handlers.cljs @@ -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}}})) diff --git a/src/cljs/commiteth/profile/page.cljs b/src/cljs/commiteth/profile/page.cljs index 382c092..c7e7c7e 100644 --- a/src/cljs/commiteth/profile/page.cljs +++ b/src/cljs/commiteth/profile/page.cljs @@ -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]