mirror of
https://github.com/penpot/penpot.git
synced 2025-05-04 22:35:53 +02:00
183 lines
6.1 KiB
Clojure
183 lines
6.1 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.loggers.webhooks
|
|
"A mattermost integration for error reporting."
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[app.common.logging :as l]
|
|
[app.common.transit :as t]
|
|
[app.common.uri :as uri]
|
|
[app.config :as cf]
|
|
[app.db :as db]
|
|
[app.http.client :as http]
|
|
[app.util.time :as dt]
|
|
[app.worker :as wrk]
|
|
[clojure.data.json :as json]
|
|
[clojure.spec.alpha :as s]
|
|
[cuerdas.core :as str]
|
|
[integrant.core :as ig]))
|
|
|
|
;; --- HELPERS
|
|
|
|
(defn key-fn
|
|
[k & keys]
|
|
(fn [params]
|
|
(reduce #(dm/str %1 ":" (get params %2))
|
|
(dm/str (get params k))
|
|
keys)))
|
|
|
|
;; --- PROC
|
|
|
|
(defn- lookup-webhooks-by-team
|
|
[pool team-id]
|
|
(db/exec! pool ["select w.* from webhook as w where team_id=? and is_active=true" team-id]))
|
|
|
|
(defn- lookup-webhooks-by-project
|
|
[pool project-id]
|
|
(let [sql [(str "select w.* from webhook as w"
|
|
" join project as p on (p.team_id = w.team_id)"
|
|
" where p.id = ? and w.is_active = true")
|
|
project-id]]
|
|
(db/exec! pool sql)))
|
|
|
|
(defn- lookup-webhooks-by-file
|
|
[pool file-id]
|
|
(let [sql [(str "select w.* from webhook as w"
|
|
" join project as p on (p.team_id = w.team_id)"
|
|
" join file as f on (f.project_id = p.id)"
|
|
" where f.id = ? and w.is_active = true")
|
|
file-id]]
|
|
(db/exec! pool sql)))
|
|
|
|
(defn- lookup-webhooks
|
|
[{:keys [::db/pool]} {:keys [props] :as event}]
|
|
(or (some->> (:team-id props) (lookup-webhooks-by-team pool))
|
|
(some->> (:project-id props) (lookup-webhooks-by-project pool))
|
|
(some->> (:file-id props) (lookup-webhooks-by-file pool))))
|
|
|
|
(defmethod ig/pre-init-spec ::process-event-handler [_]
|
|
(s/keys :req [::db/pool]))
|
|
|
|
(defmethod ig/init-key ::process-event-handler
|
|
[_ {:keys [::db/pool] :as cfg}]
|
|
(fn [{:keys [props] :as task}]
|
|
(let [event (::event props)]
|
|
(l/dbg :hint "process webhook event" :name (:name event))
|
|
|
|
(when-let [items (lookup-webhooks cfg event)]
|
|
(l/trc :hint "webhooks found for event" :total (count items))
|
|
|
|
(db/with-atomic [conn pool]
|
|
(doseq [item items]
|
|
(wrk/submit! ::wrk/conn conn
|
|
::wrk/task :run-webhook
|
|
::wrk/queue :webhooks
|
|
::wrk/max-retries 3
|
|
::event event
|
|
::config item)))))))
|
|
|
|
;; --- RUN
|
|
|
|
(declare interpret-exception)
|
|
(declare interpret-response)
|
|
|
|
(def json-write-opts
|
|
{:key-fn str/camel
|
|
:indent true})
|
|
|
|
(defmethod ig/pre-init-spec ::run-webhook-handler [_]
|
|
(s/keys :req [::http/client ::db/pool]))
|
|
|
|
(defmethod ig/prep-key ::run-webhook-handler
|
|
[_ cfg]
|
|
(merge {::max-errors 3} (d/without-nils cfg)))
|
|
|
|
(defmethod ig/init-key ::run-webhook-handler
|
|
[_ {:keys [::db/pool ::max-errors] :as cfg}]
|
|
(letfn [(update-webhook! [whook err]
|
|
(if err
|
|
(let [sql [(str "update webhook "
|
|
" set error_code=?, "
|
|
" error_count=error_count+1 "
|
|
" where id=?")
|
|
err
|
|
(:id whook)]
|
|
res (db/exec-one! pool sql {::db/return-keys true})]
|
|
(when (>= (:error-count res) max-errors)
|
|
(db/update! pool :webhook
|
|
{:is-active false}
|
|
{:id (:id whook)})))
|
|
|
|
(db/update! pool :webhook
|
|
{:updated-at (dt/now)
|
|
:error-code nil
|
|
:error-count 0}
|
|
{:id (:id whook)})))
|
|
|
|
(report-delivery! [whook req rsp err]
|
|
(db/insert! pool :webhook-delivery
|
|
{:webhook-id (:id whook)
|
|
:created-at (dt/now)
|
|
:error-code err
|
|
:req-data (db/tjson req)
|
|
:rsp-data (db/tjson rsp)}))]
|
|
|
|
(fn [{:keys [props] :as task}]
|
|
(let [event (::event props)
|
|
whook (::config props)
|
|
|
|
body (case (:mtype whook)
|
|
"application/json" (json/write-str event json-write-opts)
|
|
"application/transit+json" (t/encode-str event)
|
|
"application/x-www-form-urlencoded" (uri/map->query-string event))]
|
|
|
|
(l/dbg :hint "run webhook"
|
|
:event-name (:name event)
|
|
:webhook-id (:id whook)
|
|
:webhook-uri (:uri whook)
|
|
:webhook-mtype (:mtype whook))
|
|
|
|
(let [req {:uri (:uri whook)
|
|
:headers {"content-type" (:mtype whook)
|
|
"user-agent" (str/ffmt "penpot/%" (:main cf/version))}
|
|
:timeout (dt/duration "4s")
|
|
:method :post
|
|
:body body}]
|
|
(try
|
|
(let [rsp (http/req! cfg req {:response-type :input-stream :sync? true})
|
|
err (interpret-response rsp)]
|
|
(report-delivery! whook req rsp err)
|
|
(update-webhook! whook err))
|
|
(catch Throwable cause
|
|
(let [err (interpret-exception cause)]
|
|
(report-delivery! whook req nil err)
|
|
(update-webhook! whook err)
|
|
(when (= err "unknown")
|
|
(l/err :hint "unknown error on webhook request"
|
|
:cause cause))))))))))
|
|
|
|
(defn interpret-response
|
|
[{:keys [status] :as response}]
|
|
(when-not (or (= 200 status)
|
|
(= 204 status))
|
|
(str/ffmt "unexpected-status:%" status)))
|
|
|
|
(defn interpret-exception
|
|
[cause]
|
|
(cond
|
|
(instance? javax.net.ssl.SSLHandshakeException cause)
|
|
"ssl-validation-error"
|
|
|
|
(instance? java.net.ConnectException cause)
|
|
"connection-error"
|
|
|
|
(instance? java.lang.IllegalArgumentException cause)
|
|
"invalid-uri"
|
|
|
|
(instance? java.net.http.HttpConnectTimeoutException cause)
|
|
"timeout"))
|