mirror of
https://github.com/penpot/penpot.git
synced 2025-05-02 13:25:53 +02:00
♻️ Minor code reorganization.
Improves modularity and reusability and allows usage of backend code as a library.
This commit is contained in:
parent
59a45530a8
commit
0926fbcbc6
27 changed files with 704 additions and 791 deletions
|
@ -70,7 +70,7 @@
|
||||||
[]
|
[]
|
||||||
(alter-var-root #'system (fn [sys]
|
(alter-var-root #'system (fn [sys]
|
||||||
(when sys (ig/halt! sys))
|
(when sys (ig/halt! sys))
|
||||||
(-> (main/build-system-config cfg/config)
|
(-> main/system-config
|
||||||
(ig/prep)
|
(ig/prep)
|
||||||
(ig/init))))
|
(ig/init))))
|
||||||
:started)
|
:started)
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cfg]
|
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.main :as main]
|
[app.main :as main]
|
||||||
[app.rpc.mutations.profile :as profile]
|
[app.rpc.mutations.profile :as profile]
|
||||||
|
@ -233,7 +232,7 @@
|
||||||
|
|
||||||
(defn run
|
(defn run
|
||||||
[{:keys [preset] :or {preset :small}}]
|
[{:keys [preset] :or {preset :small}}]
|
||||||
(let [config (select-keys (main/build-system-config cfg/config)
|
(let [config (select-keys main/system-config
|
||||||
[:app.db/pool
|
[:app.db/pool
|
||||||
:app.telemetry/migrations
|
:app.telemetry/migrations
|
||||||
:app.migrations/migrations
|
:app.migrations/migrations
|
||||||
|
|
|
@ -5,12 +5,11 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.cli.manage
|
(ns app.cli.manage
|
||||||
"A manage cli api."
|
"A manage cli api."
|
||||||
(:require
|
(:require
|
||||||
[app.config :as cfg]
|
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.main :as main]
|
[app.main :as main]
|
||||||
[app.rpc.mutations.profile :as profile]
|
[app.rpc.mutations.profile :as profile]
|
||||||
|
@ -26,7 +25,7 @@
|
||||||
|
|
||||||
(defn init-system
|
(defn init-system
|
||||||
[]
|
[]
|
||||||
(let [data (-> (main/build-system-config cfg/config)
|
(let [data (-> main/system-config
|
||||||
(select-keys [:app.db/pool :app.metrics/metrics])
|
(select-keys [:app.db/pool :app.metrics/metrics])
|
||||||
(assoc :app.migrations/all {}))]
|
(assoc :app.migrations/all {}))]
|
||||||
(-> data ig/prep ig/init)))
|
(-> data ig/prep ig/init)))
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
(ns app.cli.migrate-media
|
(ns app.cli.migrate-media
|
||||||
(:require
|
(:require
|
||||||
[app.common.media :as cm]
|
[app.common.media :as cm]
|
||||||
[app.config :as cfg]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.main :as main]
|
[app.main :as main]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
(defn run
|
(defn run
|
||||||
[]
|
[]
|
||||||
(let [config (select-keys (main/build-system-config cfg/config)
|
(let [config (select-keys main/system-config
|
||||||
[:app.db/pool
|
[:app.db/pool
|
||||||
:app.migrations/migrations
|
:app.migrations/migrations
|
||||||
:app.metrics/metrics
|
:app.metrics/metrics
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
(->> (db/exec! conn ["select * from profile"])
|
(->> (db/exec! conn ["select * from profile"])
|
||||||
(filter #(not (str/empty? (:photo %))))
|
(filter #(not (str/empty? (:photo %))))
|
||||||
(seq)))]
|
(seq)))]
|
||||||
(let [base (fs/path (:storage-fs-old-directory cfg/config))
|
(let [base (fs/path (cf/get :storage-fs-old-directory))
|
||||||
storage (-> (:app.storage/storage system)
|
storage (-> (:app.storage/storage system)
|
||||||
(assoc :conn conn))]
|
(assoc :conn conn))]
|
||||||
(doseq [profile (retrieve-profiles conn)]
|
(doseq [profile (retrieve-profiles conn)]
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
(->> (db/exec! conn ["select * from team"])
|
(->> (db/exec! conn ["select * from team"])
|
||||||
(filter #(not (str/empty? (:photo %))))
|
(filter #(not (str/empty? (:photo %))))
|
||||||
(seq)))]
|
(seq)))]
|
||||||
(let [base (fs/path (:storage-fs-old-directory cfg/config))
|
(let [base (fs/path (cf/get :storage-fs-old-directory))
|
||||||
storage (-> (:app.storage/storage system)
|
storage (-> (:app.storage/storage system)
|
||||||
(assoc :conn conn))]
|
(assoc :conn conn))]
|
||||||
(doseq [team (retrieve-teams conn)]
|
(doseq [team (retrieve-teams conn)]
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
from file_media_object as fmo
|
from file_media_object as fmo
|
||||||
join file_media_thumbnail as fth on (fth.media_object_id = fmo.id)"])
|
join file_media_thumbnail as fth on (fth.media_object_id = fmo.id)"])
|
||||||
(seq)))]
|
(seq)))]
|
||||||
(let [base (fs/path (:storage-fs-old-directory cfg/config))
|
(let [base (fs/path (cf/get :storage-fs-old-directory))
|
||||||
storage (-> (:app.storage/storage system)
|
storage (-> (:app.storage/storage system)
|
||||||
(assoc :conn conn))]
|
(assoc :conn conn))]
|
||||||
(doseq [mobj (retrieve-media-objects conn)]
|
(doseq [mobj (retrieve-media-objects conn)]
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.config
|
(ns app.config
|
||||||
"A configuration management."
|
"A configuration management."
|
||||||
|
@ -15,10 +15,19 @@
|
||||||
[app.common.version :as v]
|
[app.common.version :as v]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[clojure.core :as c]
|
[clojure.core :as c]
|
||||||
|
[clojure.pprint :as pprint]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[environ.core :refer [env]]))
|
[environ.core :refer [env]]))
|
||||||
|
|
||||||
|
(prefer-method print-method
|
||||||
|
clojure.lang.IRecord
|
||||||
|
clojure.lang.IDeref)
|
||||||
|
|
||||||
|
(prefer-method pprint/simple-dispatch
|
||||||
|
clojure.lang.IPersistentMap
|
||||||
|
clojure.lang.IDeref)
|
||||||
|
|
||||||
(def defaults
|
(def defaults
|
||||||
{:http-server-port 6060
|
{:http-server-port 6060
|
||||||
:host "devenv"
|
:host "devenv"
|
||||||
|
@ -221,39 +230,31 @@
|
||||||
::telemetry-server-enabled
|
::telemetry-server-enabled
|
||||||
::telemetry-server-port
|
::telemetry-server-port
|
||||||
::telemetry-uri
|
::telemetry-uri
|
||||||
|
::telemetry-referer
|
||||||
::telemetry-with-taiga
|
::telemetry-with-taiga
|
||||||
::tenant]))
|
::tenant]))
|
||||||
|
|
||||||
(defn- env->config
|
(defn read-env
|
||||||
[env]
|
[prefix]
|
||||||
(reduce-kv
|
(let [prefix (str prefix "-")
|
||||||
(fn [acc k v]
|
len (count prefix)]
|
||||||
(cond-> acc
|
(reduce-kv
|
||||||
(str/starts-with? (name k) "penpot-")
|
(fn [acc k v]
|
||||||
(assoc (keyword (subs (name k) 7)) v)
|
(cond-> acc
|
||||||
|
(str/starts-with? (name k) prefix)
|
||||||
|
(assoc (keyword (subs (name k) len)) v)))
|
||||||
|
{}
|
||||||
|
env)))
|
||||||
|
|
||||||
(str/starts-with? (name k) "app-")
|
|
||||||
(assoc (keyword (subs (name k) 4)) v)))
|
|
||||||
{}
|
|
||||||
env))
|
|
||||||
|
|
||||||
(defn- read-config
|
(defn- read-config
|
||||||
[env]
|
[]
|
||||||
(->> (env->config env)
|
(->> (read-env "penpot")
|
||||||
(merge defaults)
|
(merge defaults)
|
||||||
(us/conform ::config)))
|
(us/conform ::config)))
|
||||||
|
|
||||||
(defn- read-test-config
|
|
||||||
[env]
|
|
||||||
(merge {:redis-uri "redis://redis/1"
|
|
||||||
:database-uri "postgresql://postgres/penpot_test"
|
|
||||||
:storage-fs-directory "/tmp/app/storage"
|
|
||||||
:migrations-verbose false}
|
|
||||||
(read-config env)))
|
|
||||||
|
|
||||||
(def version (v/parse "%version%"))
|
(def version (v/parse "%version%"))
|
||||||
(def config (read-config env))
|
(def config (atom (read-config)))
|
||||||
(def test-config (read-test-config env))
|
|
||||||
|
|
||||||
(def deletion-delay
|
(def deletion-delay
|
||||||
(dt/duration {:days 7}))
|
(dt/duration {:days 7}))
|
||||||
|
@ -261,6 +262,9 @@
|
||||||
(defn get
|
(defn get
|
||||||
"A configuration getter. Helps code be more testable."
|
"A configuration getter. Helps code be more testable."
|
||||||
([key]
|
([key]
|
||||||
(c/get config key))
|
(c/get @config key))
|
||||||
([key default]
|
([key default]
|
||||||
(c/get config key default)))
|
(c/get @config key default)))
|
||||||
|
|
||||||
|
;; Set value for all new threads bindings.
|
||||||
|
(alter-var-root #'*assert* (constantly (get :asserts-enabled)))
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
(ns app.db
|
(ns app.db
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
@ -48,8 +49,8 @@
|
||||||
|
|
||||||
(declare instrument-jdbc!)
|
(declare instrument-jdbc!)
|
||||||
|
|
||||||
|
(s/def ::name keyword?)
|
||||||
(s/def ::uri ::us/not-empty-string)
|
(s/def ::uri ::us/not-empty-string)
|
||||||
(s/def ::name ::us/not-empty-string)
|
|
||||||
(s/def ::min-pool-size ::us/integer)
|
(s/def ::min-pool-size ::us/integer)
|
||||||
(s/def ::max-pool-size ::us/integer)
|
(s/def ::max-pool-size ::us/integer)
|
||||||
(s/def ::migrations map?)
|
(s/def ::migrations map?)
|
||||||
|
@ -59,14 +60,14 @@
|
||||||
|
|
||||||
(defmethod ig/init-key ::pool
|
(defmethod ig/init-key ::pool
|
||||||
[_ {:keys [migrations metrics] :as cfg}]
|
[_ {:keys [migrations metrics] :as cfg}]
|
||||||
(log/infof "initialize connection pool '%s' with uri '%s'" (:name cfg) (:uri cfg))
|
(log/infof "initialize connection pool '%s' with uri '%s'" (name (:name cfg)) (:uri cfg))
|
||||||
(instrument-jdbc! (:registry metrics))
|
(instrument-jdbc! (:registry metrics))
|
||||||
(let [pool (create-pool cfg)]
|
(let [pool (create-pool cfg)]
|
||||||
(when (seq migrations)
|
(when (seq migrations)
|
||||||
(with-open [conn ^AutoCloseable (open pool)]
|
(with-open [conn ^AutoCloseable (open pool)]
|
||||||
(mg/setup! conn)
|
(mg/setup! conn)
|
||||||
(doseq [[mname steps] migrations]
|
(doseq [[name steps] migrations]
|
||||||
(mg/migrate! conn {:name (name mname) :steps steps}))))
|
(mg/migrate! conn {:name (d/name name) :steps steps}))))
|
||||||
pool))
|
pool))
|
||||||
|
|
||||||
(defmethod ig/halt-key! ::pool
|
(defmethod ig/halt-key! ::pool
|
||||||
|
@ -100,7 +101,7 @@
|
||||||
mtf (PrometheusMetricsTrackerFactory. (:registry metrics))]
|
mtf (PrometheusMetricsTrackerFactory. (:registry metrics))]
|
||||||
(doto config
|
(doto config
|
||||||
(.setJdbcUrl (str "jdbc:" dburi))
|
(.setJdbcUrl (str "jdbc:" dburi))
|
||||||
(.setPoolName (:name cfg "default"))
|
(.setPoolName (d/name (:name cfg)))
|
||||||
(.setAutoCommit true)
|
(.setAutoCommit true)
|
||||||
(.setReadOnly false)
|
(.setReadOnly false)
|
||||||
(.setConnectionTimeout 8000) ;; 8seg
|
(.setConnectionTimeout 8000) ;; 8seg
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.emails
|
(ns app.emails
|
||||||
"Main api for send emails."
|
"Main api for send emails."
|
||||||
|
@ -14,36 +14,34 @@
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.db.sql :as sql]
|
[app.db.sql :as sql]
|
||||||
[app.tasks :as tasks]
|
|
||||||
[app.util.emails :as emails]
|
[app.util.emails :as emails]
|
||||||
[clojure.spec.alpha :as s]))
|
[app.worker :as wrk]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[clojure.tools.logging :as log]
|
||||||
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
;; --- Defaults
|
|
||||||
|
|
||||||
(defn default-context
|
|
||||||
[]
|
|
||||||
{:assets-uri (:assets-uri cfg/config)
|
|
||||||
:public-uri (:public-uri cfg/config)})
|
|
||||||
|
|
||||||
;; --- Public API
|
|
||||||
|
|
||||||
|
;; --- PUBLIC API
|
||||||
(defn render
|
(defn render
|
||||||
[email-factory context]
|
[email-factory context]
|
||||||
(email-factory context))
|
(email-factory context))
|
||||||
|
|
||||||
(defn send!
|
(defn send!
|
||||||
"Schedule the email for sending."
|
"Schedule the email for sending."
|
||||||
[conn email-factory context]
|
[{:keys [::conn ::factory] :as context}]
|
||||||
(us/verify fn? email-factory)
|
(us/verify fn? factory)
|
||||||
(us/verify map? context)
|
(us/verify some? conn)
|
||||||
(let [email (email-factory context)]
|
(let [email (factory context)]
|
||||||
(tasks/submit! conn {:name "sendmail"
|
(wrk/submit! (assoc email
|
||||||
:delay 0
|
::wrk/task :sendmail
|
||||||
:max-retries 1
|
::wrk/delay 0
|
||||||
:priority 200
|
::wrk/max-retries 1
|
||||||
:props email})))
|
::wrk/priority 200
|
||||||
|
::wrk/conn conn))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- BOUNCE/COMPLAINS HANDLING
|
||||||
|
|
||||||
(def sql:profile-complaint-report
|
(def sql:profile-complaint-report
|
||||||
"select (select count(*)
|
"select (select count(*)
|
||||||
from profile_complaint_report
|
from profile_complaint_report
|
||||||
|
@ -91,7 +89,7 @@
|
||||||
(>= (count reports) threshold))))
|
(>= (count reports) threshold))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Emails
|
;; --- EMAIL FACTORIES
|
||||||
|
|
||||||
(s/def ::subject ::us/string)
|
(s/def ::subject ::us/string)
|
||||||
(s/def ::content ::us/string)
|
(s/def ::content ::us/string)
|
||||||
|
@ -101,7 +99,7 @@
|
||||||
|
|
||||||
(def feedback
|
(def feedback
|
||||||
"A profile feedback email."
|
"A profile feedback email."
|
||||||
(emails/template-factory ::feedback default-context))
|
(emails/template-factory ::feedback))
|
||||||
|
|
||||||
(s/def ::name ::us/string)
|
(s/def ::name ::us/string)
|
||||||
(s/def ::register
|
(s/def ::register
|
||||||
|
@ -109,7 +107,7 @@
|
||||||
|
|
||||||
(def register
|
(def register
|
||||||
"A new profile registration welcome email."
|
"A new profile registration welcome email."
|
||||||
(emails/template-factory ::register default-context))
|
(emails/template-factory ::register))
|
||||||
|
|
||||||
(s/def ::token ::us/string)
|
(s/def ::token ::us/string)
|
||||||
(s/def ::password-recovery
|
(s/def ::password-recovery
|
||||||
|
@ -117,7 +115,7 @@
|
||||||
|
|
||||||
(def password-recovery
|
(def password-recovery
|
||||||
"A password recovery notification email."
|
"A password recovery notification email."
|
||||||
(emails/template-factory ::password-recovery default-context))
|
(emails/template-factory ::password-recovery))
|
||||||
|
|
||||||
(s/def ::pending-email ::us/email)
|
(s/def ::pending-email ::us/email)
|
||||||
(s/def ::change-email
|
(s/def ::change-email
|
||||||
|
@ -125,7 +123,7 @@
|
||||||
|
|
||||||
(def change-email
|
(def change-email
|
||||||
"Password change confirmation email"
|
"Password change confirmation email"
|
||||||
(emails/template-factory ::change-email default-context))
|
(emails/template-factory ::change-email))
|
||||||
|
|
||||||
(s/def :internal.emails.invite-to-team/invited-by ::us/string)
|
(s/def :internal.emails.invite-to-team/invited-by ::us/string)
|
||||||
(s/def :internal.emails.invite-to-team/team ::us/string)
|
(s/def :internal.emails.invite-to-team/team ::us/string)
|
||||||
|
@ -138,4 +136,50 @@
|
||||||
|
|
||||||
(def invite-to-team
|
(def invite-to-team
|
||||||
"Teams member invitation email."
|
"Teams member invitation email."
|
||||||
(emails/template-factory ::invite-to-team default-context))
|
(emails/template-factory ::invite-to-team))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- SENDMAIL TASK
|
||||||
|
|
||||||
|
(declare send-console!)
|
||||||
|
|
||||||
|
(s/def ::username ::cfg/smtp-username)
|
||||||
|
(s/def ::password ::cfg/smtp-password)
|
||||||
|
(s/def ::tls ::cfg/smtp-tls)
|
||||||
|
(s/def ::ssl ::cfg/smtp-ssl)
|
||||||
|
(s/def ::host ::cfg/smtp-host)
|
||||||
|
(s/def ::port ::cfg/smtp-port)
|
||||||
|
(s/def ::default-reply-to ::cfg/smtp-default-reply-to)
|
||||||
|
(s/def ::default-from ::cfg/smtp-default-from)
|
||||||
|
(s/def ::enabled ::cfg/smtp-enabled)
|
||||||
|
|
||||||
|
(defmethod ig/pre-init-spec ::sendmail-handler [_]
|
||||||
|
(s/keys :req-un [::enabled]
|
||||||
|
:opt-un [::username
|
||||||
|
::password
|
||||||
|
::tls
|
||||||
|
::ssl
|
||||||
|
::host
|
||||||
|
::port
|
||||||
|
::default-from
|
||||||
|
::default-reply-to]))
|
||||||
|
|
||||||
|
(defmethod ig/init-key ::sendmail-handler
|
||||||
|
[_ cfg]
|
||||||
|
(fn [{:keys [props] :as task}]
|
||||||
|
(if (:enabled cfg)
|
||||||
|
(emails/send! cfg props)
|
||||||
|
(send-console! cfg props))))
|
||||||
|
|
||||||
|
(defn- send-console!
|
||||||
|
[cfg email]
|
||||||
|
(let [baos (java.io.ByteArrayOutputStream.)
|
||||||
|
mesg (emails/smtp-message cfg email)]
|
||||||
|
(.writeTo mesg baos)
|
||||||
|
(let [out (with-out-str
|
||||||
|
(println "email console dump:")
|
||||||
|
(println "******** start email" (:id email) "**********")
|
||||||
|
(println (.toString baos))
|
||||||
|
(println "******** end email "(:id email) "**********"))]
|
||||||
|
(log/info out))))
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.http
|
(ns app.http
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cfg]
|
|
||||||
[app.http.errors :as errors]
|
[app.http.errors :as errors]
|
||||||
[app.http.middleware :as middleware]
|
[app.http.middleware :as middleware]
|
||||||
[app.metrics :as mtx]
|
[app.metrics :as mtx]
|
||||||
|
@ -26,22 +26,24 @@
|
||||||
org.eclipse.jetty.server.handler.ErrorHandler
|
org.eclipse.jetty.server.handler.ErrorHandler
|
||||||
org.eclipse.jetty.server.handler.StatisticsHandler))
|
org.eclipse.jetty.server.handler.StatisticsHandler))
|
||||||
|
|
||||||
|
(declare router-handler)
|
||||||
|
|
||||||
(s/def ::handler fn?)
|
(s/def ::handler fn?)
|
||||||
|
(s/def ::router some?)
|
||||||
(s/def ::ws (s/map-of ::us/string fn?))
|
(s/def ::ws (s/map-of ::us/string fn?))
|
||||||
(s/def ::port ::cfg/http-server-port)
|
(s/def ::port ::us/integer)
|
||||||
(s/def ::name ::us/string)
|
(s/def ::name ::us/string)
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::server [_]
|
(defmethod ig/pre-init-spec ::server [_]
|
||||||
(s/keys :req-un [::handler ::port]
|
(s/keys :req-un [::port]
|
||||||
:opt-un [::ws ::name ::mtx/metrics]))
|
:opt-un [::ws ::name ::mtx/metrics ::router ::handler]))
|
||||||
|
|
||||||
(defmethod ig/prep-key ::server
|
(defmethod ig/prep-key ::server
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(merge {:name "http"}
|
(merge {:name "http"} (d/without-nils cfg)))
|
||||||
(d/without-nils cfg)))
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::server
|
(defmethod ig/init-key ::server
|
||||||
[_ {:keys [handler ws port name metrics] :as opts}]
|
[_ {:keys [handler router ws port name metrics] :as opts}]
|
||||||
(log/infof "starting '%s' server on port %s." name port)
|
(log/infof "starting '%s' server on port %s." name port)
|
||||||
(let [pre-start (fn [^Server server]
|
(let [pre-start (fn [^Server server]
|
||||||
(let [handler (doto (ErrorHandler.)
|
(let [handler (doto (ErrorHandler.)
|
||||||
|
@ -49,7 +51,7 @@
|
||||||
(.setServer server))]
|
(.setServer server))]
|
||||||
(.setErrorHandler server ^ErrorHandler handler)
|
(.setErrorHandler server ^ErrorHandler handler)
|
||||||
(when metrics
|
(when metrics
|
||||||
(let [stats (new StatisticsHandler)]
|
(let [stats (StatisticsHandler.)]
|
||||||
(.setHandler ^StatisticsHandler stats (.getHandler server))
|
(.setHandler ^StatisticsHandler stats (.getHandler server))
|
||||||
(.setHandler server stats)
|
(.setHandler server stats)
|
||||||
(mtx/instrument-jetty! (:registry metrics) stats)))))
|
(mtx/instrument-jetty! (:registry metrics) stats)))))
|
||||||
|
@ -63,6 +65,13 @@
|
||||||
(when (seq ws)
|
(when (seq ws)
|
||||||
{:websockets ws}))
|
{:websockets ws}))
|
||||||
|
|
||||||
|
handler (cond
|
||||||
|
(fn? handler) handler
|
||||||
|
(some? router) (router-handler router)
|
||||||
|
:else (ex/raise :type :internal
|
||||||
|
:code :invalid-argument
|
||||||
|
:hint "Missing `handler` or `router` option."))
|
||||||
|
|
||||||
server (jetty/run-jetty handler options)]
|
server (jetty/run-jetty handler options)]
|
||||||
(assoc opts :server server)))
|
(assoc opts :server server)))
|
||||||
|
|
||||||
|
@ -71,31 +80,13 @@
|
||||||
(log/infof "stoping '%s' server on port %s." name port)
|
(log/infof "stoping '%s' server on port %s." name port)
|
||||||
(jetty/stop-server server))
|
(jetty/stop-server server))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
(defn- router-handler
|
||||||
;; Http Main Handler (Router)
|
[router]
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
(let [handler (rr/ring-handler router
|
||||||
|
(rr/routes
|
||||||
(declare create-router)
|
(rr/create-resource-handler {:path "/"})
|
||||||
|
(rr/create-default-handler))
|
||||||
(s/def ::rpc map?)
|
{:middleware [middleware/server-timing]})]
|
||||||
(s/def ::session map?)
|
|
||||||
(s/def ::metrics map?)
|
|
||||||
(s/def ::oauth map?)
|
|
||||||
(s/def ::storage map?)
|
|
||||||
(s/def ::assets map?)
|
|
||||||
(s/def ::feedback fn?)
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::router [_]
|
|
||||||
(s/keys :req-un [::rpc ::session ::metrics ::oauth ::storage ::assets ::feedback]))
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::router
|
|
||||||
[_ cfg]
|
|
||||||
(let [handler (rr/ring-handler
|
|
||||||
(create-router cfg)
|
|
||||||
(rr/routes
|
|
||||||
(rr/create-resource-handler {:path "/"})
|
|
||||||
(rr/create-default-handler))
|
|
||||||
{:middleware [middleware/server-timing]})]
|
|
||||||
(fn [request]
|
(fn [request]
|
||||||
(try
|
(try
|
||||||
(handler request)
|
(handler request)
|
||||||
|
@ -104,18 +95,30 @@
|
||||||
(let [cdata (errors/get-error-context request e)]
|
(let [cdata (errors/get-error-context request e)]
|
||||||
(update-thread-context! cdata)
|
(update-thread-context! cdata)
|
||||||
(log/errorf e "unhandled exception: %s (id: %s)" (ex-message e) (str (:id cdata)))
|
(log/errorf e "unhandled exception: %s (id: %s)" (ex-message e) (str (:id cdata)))
|
||||||
{:status 500
|
{:status 500 :body "internal server error"})
|
||||||
:body "internal server error"})
|
|
||||||
(catch Throwable e
|
(catch Throwable e
|
||||||
(log/errorf e "unhandled exception: %s" (ex-message e))
|
(log/errorf e "unhandled exception: %s" (ex-message e))
|
||||||
{:status 500
|
{:status 500 :body "internal server error"})))))))
|
||||||
:body "internal server error"})))))))
|
|
||||||
|
|
||||||
(defn- create-router
|
|
||||||
[{:keys [session rpc oauth metrics svgparse assets feedback] :as cfg}]
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Http Main Handler (Router)
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(s/def ::rpc map?)
|
||||||
|
(s/def ::session map?)
|
||||||
|
(s/def ::oauth map?)
|
||||||
|
(s/def ::storage map?)
|
||||||
|
(s/def ::assets map?)
|
||||||
|
(s/def ::feedback fn?)
|
||||||
|
|
||||||
|
(defmethod ig/pre-init-spec ::router [_]
|
||||||
|
(s/keys :req-un [::rpc ::session ::mtx/metrics ::oauth ::storage ::assets ::feedback]))
|
||||||
|
|
||||||
|
(defmethod ig/init-key ::router
|
||||||
|
[_ {:keys [session rpc oauth metrics svgparse assets feedback] :as cfg}]
|
||||||
(rr/router
|
(rr/router
|
||||||
[["/metrics" {:get (:handler metrics)}]
|
[["/metrics" {:get (:handler metrics)}]
|
||||||
|
|
||||||
["/assets" {:middleware [[middleware/format-response-body]
|
["/assets" {:middleware [[middleware/format-response-body]
|
||||||
[middleware/errors errors/handle]]}
|
[middleware/errors errors/handle]]}
|
||||||
["/by-id/:id" {:get (:objects-handler assets)}]
|
["/by-id/:id" {:get (:objects-handler assets)}]
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.emails :as emails]
|
[app.emails :as eml]
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.queries.profile :as profile]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
@ -62,11 +62,12 @@
|
||||||
[pool profile params]
|
[pool profile params]
|
||||||
(let [params (us/conform ::feedback params)
|
(let [params (us/conform ::feedback params)
|
||||||
destination (cfg/get :feedback-destination)]
|
destination (cfg/get :feedback-destination)]
|
||||||
(emails/send! pool emails/feedback
|
(eml/send! {::eml/conn pool
|
||||||
{:to destination
|
::eml/factory eml/feedback
|
||||||
:profile profile
|
:to destination
|
||||||
:reply-to (:from params)
|
:profile profile
|
||||||
:email (:from params)
|
:reply-to (:from params)
|
||||||
:subject (:subject params)
|
:email (:from params)
|
||||||
:content (:content params)})
|
:subject (:subject params)
|
||||||
|
:content (:content params)})
|
||||||
nil))
|
nil))
|
||||||
|
|
|
@ -5,13 +5,12 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.http.oauth.github
|
(ns app.http.oauth.github
|
||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cfg]
|
|
||||||
[app.http.oauth.google :as gg]
|
[app.http.oauth.google :as gg]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
@ -105,7 +104,7 @@
|
||||||
state (tokens :generate {:iss :github-oauth
|
state (tokens :generate {:iss :github-oauth
|
||||||
:invitation-token invitation
|
:invitation-token invitation
|
||||||
:exp (dt/in-future "15m")})
|
:exp (dt/in-future "15m")})
|
||||||
params {:client_id (:client-id cfg/config)
|
params {:client_id (:client-id cfg)
|
||||||
:redirect_uri (build-redirect-url cfg)
|
:redirect_uri (build-redirect-url cfg)
|
||||||
:state state
|
:state state
|
||||||
:scope scope}
|
:scope scope}
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
(try
|
(try
|
||||||
(let [uri (:uri cfg)
|
(let [uri (:uri cfg)
|
||||||
text (str "Unhandled exception (@channel):\n"
|
text (str "Unhandled exception (@channel):\n"
|
||||||
"- detail: " (:public-uri cfg/config) "/dbg/error-by-id/" id "\n"
|
"- detail: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n"
|
||||||
"- host: `" host "`\n"
|
"- host: `" host "`\n"
|
||||||
"- version: `" version "`\n"
|
"- version: `" version "`\n"
|
||||||
(when error
|
(when error
|
||||||
|
|
|
@ -5,353 +5,332 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.main
|
(ns app.main
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.config :as cfg]
|
[app.config :as cf]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[clojure.pprint :as pprint]
|
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
;; Set value for all new threads bindings.
|
(def system-config
|
||||||
(alter-var-root #'*assert* (constantly (:asserts-enabled cfg/config)))
|
{:app.db/pool
|
||||||
|
{:uri (cf/get :database-uri)
|
||||||
|
:username (cf/get :database-username)
|
||||||
|
:password (cf/get :database-password)
|
||||||
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:migrations (ig/ref :app.migrations/all)
|
||||||
|
:name :main
|
||||||
|
:min-pool-size 0
|
||||||
|
:max-pool-size 20}
|
||||||
|
|
||||||
(derive :app.telemetry/server :app.http/server)
|
:app.metrics/metrics
|
||||||
|
{:definitions
|
||||||
|
{:profile-register
|
||||||
|
{:name "actions_profile_register_count"
|
||||||
|
:help "A global counter of user registrations."
|
||||||
|
:type :counter}
|
||||||
|
:profile-activation
|
||||||
|
{:name "actions_profile_activation_count"
|
||||||
|
:help "A global counter of profile activations"
|
||||||
|
:type :counter}}}
|
||||||
|
|
||||||
;; --- Entry point
|
:app.migrations/all
|
||||||
|
{:main (ig/ref :app.migrations/migrations)}
|
||||||
|
|
||||||
(defn build-system-config
|
:app.migrations/migrations
|
||||||
[config]
|
{}
|
||||||
(d/deep-merge
|
|
||||||
{:app.db/pool
|
|
||||||
{:uri (:database-uri config)
|
|
||||||
:username (:database-username config)
|
|
||||||
:password (:database-password config)
|
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
|
||||||
:migrations (ig/ref :app.migrations/all)
|
|
||||||
:name "main"
|
|
||||||
:min-pool-size 0
|
|
||||||
:max-pool-size 20}
|
|
||||||
|
|
||||||
:app.metrics/metrics
|
:app.msgbus/msgbus
|
||||||
{:definitions
|
{:backend (cf/get :msgbus-backend :redis)
|
||||||
{:profile-register
|
:redis-uri (cf/get :redis-uri)}
|
||||||
{:name "actions_profile_register_count"
|
|
||||||
:help "A global counter of user registrations."
|
|
||||||
:type :counter}
|
|
||||||
:profile-activation
|
|
||||||
{:name "actions_profile_activation_count"
|
|
||||||
:help "A global counter of profile activations"
|
|
||||||
:type :counter}}}
|
|
||||||
|
|
||||||
:app.migrations/all
|
:app.tokens/tokens
|
||||||
{:main (ig/ref :app.migrations/migrations)
|
{:sprops (ig/ref :app.setup/props)}
|
||||||
:telemetry (ig/ref :app.telemetry/migrations)}
|
|
||||||
|
|
||||||
:app.migrations/migrations
|
:app.storage/gc-deleted-task
|
||||||
{}
|
{:pool (ig/ref :app.db/pool)
|
||||||
|
:storage (ig/ref :app.storage/storage)
|
||||||
|
:min-age (dt/duration {:hours 2})}
|
||||||
|
|
||||||
:app.telemetry/migrations
|
:app.storage/gc-touched-task
|
||||||
{}
|
{:pool (ig/ref :app.db/pool)}
|
||||||
|
|
||||||
:app.msgbus/msgbus
|
:app.storage/recheck-task
|
||||||
{:backend (:msgbus-backend config :redis)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:redis-uri (:redis-uri config)}
|
:storage (ig/ref :app.storage/storage)}
|
||||||
|
|
||||||
:app.tokens/tokens
|
:app.http.session/session
|
||||||
{:sprops (ig/ref :app.setup/props)}
|
{:pool (ig/ref :app.db/pool)
|
||||||
|
:cookie-name (cf/get :http-session-cookie-name)}
|
||||||
|
|
||||||
:app.storage/gc-deleted-task
|
:app.http.session/gc-task
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:storage (ig/ref :app.storage/storage)
|
:max-age (cf/get :http-session-idle-max-age)}
|
||||||
:min-age (dt/duration {:hours 2})}
|
|
||||||
|
|
||||||
:app.storage/gc-touched-task
|
:app.http.session/updater
|
||||||
{:pool (ig/ref :app.db/pool)}
|
{:pool (ig/ref :app.db/pool)
|
||||||
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:executor (ig/ref :app.worker/executor)
|
||||||
|
:session (ig/ref :app.http.session/session)
|
||||||
|
:max-batch-age (cf/get :http-session-updater-batch-max-age)
|
||||||
|
:max-batch-size (cf/get :http-session-updater-batch-max-size)}
|
||||||
|
|
||||||
:app.storage/recheck-task
|
:app.http.awsns/handler
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:tokens (ig/ref :app.tokens/tokens)
|
||||||
:storage (ig/ref :app.storage/storage)}
|
:pool (ig/ref :app.db/pool)}
|
||||||
|
|
||||||
:app.http.session/session
|
:app.http/server
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:port (cf/get :http-server-port)
|
||||||
:cookie-name (:http-session-cookie-name config)}
|
:router (ig/ref :app.http/router)
|
||||||
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:ws {"/ws/notifications" (ig/ref :app.notifications/handler)}}
|
||||||
|
|
||||||
:app.http.session/gc-task
|
:app.http/router
|
||||||
{:pool (ig/ref :app.db/pool)
|
{
|
||||||
:max-age (:http-session-idle-max-age config)}
|
:rpc (ig/ref :app.rpc/rpc)
|
||||||
|
:session (ig/ref :app.http.session/session)
|
||||||
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
|
:public-uri (cf/get :public-uri)
|
||||||
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:oauth (ig/ref :app.http.oauth/all)
|
||||||
|
:assets (ig/ref :app.http.assets/handlers)
|
||||||
|
:svgparse (ig/ref :app.svgparse/handler)
|
||||||
|
:storage (ig/ref :app.storage/storage)
|
||||||
|
:sns-webhook (ig/ref :app.http.awsns/handler)
|
||||||
|
:feedback (ig/ref :app.http.feedback/handler)
|
||||||
|
:error-report-handler (ig/ref :app.loggers.mattermost/handler)}
|
||||||
|
|
||||||
:app.http.session/updater
|
:app.http.assets/handlers
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:metrics (ig/ref :app.metrics/metrics)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:assets-path (cf/get :assets-path)
|
||||||
:executor (ig/ref :app.worker/executor)
|
:storage (ig/ref :app.storage/storage)
|
||||||
:session (ig/ref :app.http.session/session)
|
:cache-max-age (dt/duration {:hours 24})
|
||||||
:max-batch-age (:http-session-updater-batch-max-age config)
|
:signature-max-age (dt/duration {:hours 24 :minutes 5})}
|
||||||
:max-batch-size (:http-session-updater-batch-max-size config)}
|
|
||||||
|
|
||||||
:app.http.awsns/handler
|
:app.http.feedback/handler
|
||||||
{:tokens (ig/ref :app.tokens/tokens)
|
{:pool (ig/ref :app.db/pool)}
|
||||||
:pool (ig/ref :app.db/pool)}
|
|
||||||
|
|
||||||
:app.http/server
|
:app.http.oauth/all
|
||||||
{:port (:http-server-port config)
|
{:google (ig/ref :app.http.oauth/google)
|
||||||
:handler (ig/ref :app.http/router)
|
:gitlab (ig/ref :app.http.oauth/gitlab)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:github (ig/ref :app.http.oauth/github)}
|
||||||
:ws {"/ws/notifications" (ig/ref :app.notifications/handler)}}
|
|
||||||
|
|
||||||
:app.http/router
|
:app.http.oauth/google
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
{:rpc (ig/ref :app.rpc/rpc)
|
||||||
:session (ig/ref :app.http.session/session)
|
:session (ig/ref :app.http.session/session)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
:public-uri (:public-uri config)
|
:public-uri (cf/get :public-uri)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:client-id (cf/get :google-client-id)
|
||||||
:oauth (ig/ref :app.http.oauth/all)
|
:client-secret (cf/get :google-client-secret)}
|
||||||
:assets (ig/ref :app.http.assets/handlers)
|
|
||||||
:svgparse (ig/ref :app.svgparse/handler)
|
|
||||||
:storage (ig/ref :app.storage/storage)
|
|
||||||
:sns-webhook (ig/ref :app.http.awsns/handler)
|
|
||||||
:feedback (ig/ref :app.http.feedback/handler)
|
|
||||||
:error-report-handler (ig/ref :app.loggers.mattermost/handler)}
|
|
||||||
|
|
||||||
:app.http.assets/handlers
|
:app.http.oauth/github
|
||||||
{:metrics (ig/ref :app.metrics/metrics)
|
{:rpc (ig/ref :app.rpc/rpc)
|
||||||
:assets-path (:assets-path config)
|
:session (ig/ref :app.http.session/session)
|
||||||
:storage (ig/ref :app.storage/storage)
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
:cache-max-age (dt/duration {:hours 24})
|
:public-uri (cf/get :public-uri)
|
||||||
:signature-max-age (dt/duration {:hours 24 :minutes 5})}
|
:client-id (cf/get :github-client-id)
|
||||||
|
:client-secret (cf/get :github-client-secret)}
|
||||||
|
|
||||||
:app.http.feedback/handler
|
:app.http.oauth/gitlab
|
||||||
{:pool (ig/ref :app.db/pool)}
|
{:rpc (ig/ref :app.rpc/rpc)
|
||||||
|
:session (ig/ref :app.http.session/session)
|
||||||
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
|
:public-uri (cf/get :public-uri)
|
||||||
|
:base-uri (cf/get :gitlab-base-uri)
|
||||||
|
:client-id (cf/get :gitlab-client-id)
|
||||||
|
:client-secret (cf/get :gitlab-client-secret)}
|
||||||
|
|
||||||
:app.http.oauth/all
|
:app.svgparse/svgc
|
||||||
{:google (ig/ref :app.http.oauth/google)
|
{:metrics (ig/ref :app.metrics/metrics)}
|
||||||
:gitlab (ig/ref :app.http.oauth/gitlab)
|
|
||||||
:github (ig/ref :app.http.oauth/github)}
|
|
||||||
|
|
||||||
:app.http.oauth/google
|
;; HTTP Handler for SVG parsing
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
:app.svgparse/handler
|
||||||
:session (ig/ref :app.http.session/session)
|
{:metrics (ig/ref :app.metrics/metrics)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
:svgc (ig/ref :app.svgparse/svgc)}
|
||||||
:public-uri (:public-uri config)
|
|
||||||
:client-id (:google-client-id config)
|
|
||||||
:client-secret (:google-client-secret config)}
|
|
||||||
|
|
||||||
:app.http.oauth/github
|
;; RLimit definition for password hashing
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
:app.rlimits/password
|
||||||
:session (ig/ref :app.http.session/session)
|
(cf/get :rlimits-password)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
|
||||||
:public-uri (:public-uri config)
|
|
||||||
:client-id (:github-client-id config)
|
|
||||||
:client-secret (:github-client-secret config)}
|
|
||||||
|
|
||||||
:app.http.oauth/gitlab
|
;; RLimit definition for image processing
|
||||||
{:rpc (ig/ref :app.rpc/rpc)
|
:app.rlimits/image
|
||||||
:session (ig/ref :app.http.session/session)
|
(cf/get :rlimits-image)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
|
||||||
:public-uri (:public-uri config)
|
|
||||||
:base-uri (:gitlab-base-uri config)
|
|
||||||
:client-id (:gitlab-client-id config)
|
|
||||||
:client-secret (:gitlab-client-secret config)}
|
|
||||||
|
|
||||||
;; HTTP Handler for SVG parsing
|
;; A collection of rlimits as hash-map.
|
||||||
:app.svgparse/handler
|
:app.rlimits/all
|
||||||
{:metrics (ig/ref :app.metrics/metrics)}
|
{:password (ig/ref :app.rlimits/password)
|
||||||
|
:image (ig/ref :app.rlimits/image)}
|
||||||
|
|
||||||
;; RLimit definition for password hashing
|
:app.rpc/rpc
|
||||||
:app.rlimits/password
|
{:pool (ig/ref :app.db/pool)
|
||||||
(:rlimits-password config)
|
:session (ig/ref :app.http.session/session)
|
||||||
|
:tokens (ig/ref :app.tokens/tokens)
|
||||||
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:storage (ig/ref :app.storage/storage)
|
||||||
|
:msgbus (ig/ref :app.msgbus/msgbus)
|
||||||
|
:rlimits (ig/ref :app.rlimits/all)
|
||||||
|
:svgc (ig/ref :app.svgparse/svgc)
|
||||||
|
:public-uri (cf/get :public-uri)}
|
||||||
|
|
||||||
;; RLimit definition for image processing
|
:app.notifications/handler
|
||||||
:app.rlimits/image
|
{:msgbus (ig/ref :app.msgbus/msgbus)
|
||||||
(:rlimits-image config)
|
:pool (ig/ref :app.db/pool)
|
||||||
|
:session (ig/ref :app.http.session/session)
|
||||||
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:executor (ig/ref :app.worker/executor)}
|
||||||
|
|
||||||
;; A collection of rlimits as hash-map.
|
:app.worker/executor
|
||||||
:app.rlimits/all
|
{:min-threads 0
|
||||||
{:password (ig/ref :app.rlimits/password)
|
:max-threads 256
|
||||||
:image (ig/ref :app.rlimits/image)}
|
:idle-timeout 60000
|
||||||
|
:name :worker}
|
||||||
|
|
||||||
:app.rpc/rpc
|
:app.worker/worker
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:executor (ig/ref :app.worker/executor)
|
||||||
:session (ig/ref :app.http.session/session)
|
:tasks (ig/ref :app.worker/registry)
|
||||||
:tokens (ig/ref :app.tokens/tokens)
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:pool (ig/ref :app.db/pool)}
|
||||||
:storage (ig/ref :app.storage/storage)
|
|
||||||
:msgbus (ig/ref :app.msgbus/msgbus)
|
|
||||||
:rlimits (ig/ref :app.rlimits/all)}
|
|
||||||
|
|
||||||
:app.notifications/handler
|
:app.worker/scheduler
|
||||||
{:msgbus (ig/ref :app.msgbus/msgbus)
|
{:executor (ig/ref :app.worker/executor)
|
||||||
:pool (ig/ref :app.db/pool)
|
:tasks (ig/ref :app.worker/registry)
|
||||||
:session (ig/ref :app.http.session/session)
|
:pool (ig/ref :app.db/pool)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:schedule
|
||||||
:executor (ig/ref :app.worker/executor)}
|
[{:cron #app/cron "0 0 0 */1 * ? *" ;; daily
|
||||||
|
:task :file-media-gc}
|
||||||
|
|
||||||
:app.worker/executor
|
{:cron #app/cron "0 0 */1 * * ?" ;; hourly
|
||||||
{:name "worker"}
|
:task :file-xlog-gc}
|
||||||
|
|
||||||
:app.worker/worker
|
{:cron #app/cron "0 0 1 */1 * ?" ;; daily (1 hour shift)
|
||||||
{:executor (ig/ref :app.worker/executor)
|
:task :storage-deleted-gc}
|
||||||
:pool (ig/ref :app.db/pool)
|
|
||||||
:tasks (ig/ref :app.tasks/registry)}
|
|
||||||
|
|
||||||
:app.worker/scheduler
|
{:cron #app/cron "0 0 2 */1 * ?" ;; daily (2 hour shift)
|
||||||
{:executor (ig/ref :app.worker/executor)
|
:task :storage-touched-gc}
|
||||||
:pool (ig/ref :app.db/pool)
|
|
||||||
:tasks (ig/ref :app.tasks/registry)
|
|
||||||
:schedule
|
|
||||||
[{:id "file-media-gc"
|
|
||||||
:cron #app/cron "0 0 0 */1 * ? *" ;; daily
|
|
||||||
:task :file-media-gc}
|
|
||||||
|
|
||||||
{:id "file-xlog-gc"
|
{:cron #app/cron "0 0 3 */1 * ?" ;; daily (3 hour shift)
|
||||||
:cron #app/cron "0 0 */1 * * ?" ;; hourly
|
:task :session-gc}
|
||||||
:task :file-xlog-gc}
|
|
||||||
|
|
||||||
{:id "storage-deleted-gc"
|
{:cron #app/cron "0 0 */1 * * ?" ;; hourly
|
||||||
:cron #app/cron "0 0 1 */1 * ?" ;; daily (1 hour shift)
|
:task :storage-recheck}
|
||||||
:task :storage-deleted-gc}
|
|
||||||
|
|
||||||
{:id "storage-touched-gc"
|
{:cron #app/cron "0 0 0 */1 * ?" ;; daily
|
||||||
:cron #app/cron "0 0 2 */1 * ?" ;; daily (2 hour shift)
|
:task :tasks-gc}
|
||||||
:task :storage-touched-gc}
|
|
||||||
|
|
||||||
{:id "session-gc"
|
(when (cf/get :telemetry-enabled)
|
||||||
:cron #app/cron "0 0 3 */1 * ?" ;; daily (3 hour shift)
|
{:cron #app/cron "0 0 */6 * * ?" ;; every 6h
|
||||||
:task :session-gc}
|
:uri (cf/get :telemetry-uri)
|
||||||
|
:task :telemetry})]}
|
||||||
|
|
||||||
{:id "storage-recheck"
|
:app.worker/registry
|
||||||
:cron #app/cron "0 0 */1 * * ?" ;; hourly
|
{:metrics (ig/ref :app.metrics/metrics)
|
||||||
:task :storage-recheck}
|
:tasks
|
||||||
|
{:sendmail (ig/ref :app.emails/sendmail-handler)
|
||||||
|
:delete-object (ig/ref :app.tasks.delete-object/handler)
|
||||||
|
:delete-profile (ig/ref :app.tasks.delete-profile/handler)
|
||||||
|
:file-media-gc (ig/ref :app.tasks.file-media-gc/handler)
|
||||||
|
:file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler)
|
||||||
|
:storage-deleted-gc (ig/ref :app.storage/gc-deleted-task)
|
||||||
|
:storage-touched-gc (ig/ref :app.storage/gc-touched-task)
|
||||||
|
:storage-recheck (ig/ref :app.storage/recheck-task)
|
||||||
|
:tasks-gc (ig/ref :app.tasks.tasks-gc/handler)
|
||||||
|
:telemetry (ig/ref :app.tasks.telemetry/handler)
|
||||||
|
:session-gc (ig/ref :app.http.session/gc-task)}}
|
||||||
|
|
||||||
{:id "tasks-gc"
|
:app.emails/sendmail-handler
|
||||||
:cron #app/cron "0 0 0 */1 * ?" ;; daily
|
{:host (cf/get :smtp-host)
|
||||||
:task :tasks-gc}
|
:port (cf/get :smtp-port)
|
||||||
|
:ssl (cf/get :smtp-ssl)
|
||||||
|
:tls (cf/get :smtp-tls)
|
||||||
|
:enabled (cf/get :smtp-enabled)
|
||||||
|
:username (cf/get :smtp-username)
|
||||||
|
:password (cf/get :smtp-password)
|
||||||
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:default-reply-to (cf/get :smtp-default-reply-to)
|
||||||
|
:default-from (cf/get :smtp-default-from)}
|
||||||
|
|
||||||
(when (:telemetry-enabled config)
|
:app.tasks.tasks-gc/handler
|
||||||
{:id "telemetry"
|
{:pool (ig/ref :app.db/pool)
|
||||||
:cron #app/cron "0 0 */6 * * ?" ;; every 6h
|
:max-age (dt/duration {:hours 24})
|
||||||
:uri (:telemetry-uri config)
|
:metrics (ig/ref :app.metrics/metrics)}
|
||||||
:task :telemetry})]}
|
|
||||||
|
|
||||||
:app.tasks/registry
|
:app.tasks.delete-object/handler
|
||||||
{:metrics (ig/ref :app.metrics/metrics)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:tasks
|
:metrics (ig/ref :app.metrics/metrics)}
|
||||||
{:sendmail (ig/ref :app.tasks.sendmail/handler)
|
|
||||||
:delete-object (ig/ref :app.tasks.delete-object/handler)
|
|
||||||
:delete-profile (ig/ref :app.tasks.delete-profile/handler)
|
|
||||||
:file-media-gc (ig/ref :app.tasks.file-media-gc/handler)
|
|
||||||
:file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler)
|
|
||||||
:storage-deleted-gc (ig/ref :app.storage/gc-deleted-task)
|
|
||||||
:storage-touched-gc (ig/ref :app.storage/gc-touched-task)
|
|
||||||
:storage-recheck (ig/ref :app.storage/recheck-task)
|
|
||||||
:tasks-gc (ig/ref :app.tasks.tasks-gc/handler)
|
|
||||||
:telemetry (ig/ref :app.tasks.telemetry/handler)
|
|
||||||
:session-gc (ig/ref :app.http.session/gc-task)}}
|
|
||||||
|
|
||||||
:app.tasks.sendmail/handler
|
:app.tasks.delete-storage-object/handler
|
||||||
{:host (:smtp-host config)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:port (:smtp-port config)
|
:storage (ig/ref :app.storage/storage)
|
||||||
:ssl (:smtp-ssl config)
|
:metrics (ig/ref :app.metrics/metrics)}
|
||||||
:tls (:smtp-tls config)
|
|
||||||
:enabled (:smtp-enabled config)
|
|
||||||
:username (:smtp-username config)
|
|
||||||
:password (:smtp-password config)
|
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
|
||||||
:default-reply-to (:smtp-default-reply-to config)
|
|
||||||
:default-from (:smtp-default-from config)}
|
|
||||||
|
|
||||||
:app.tasks.tasks-gc/handler
|
:app.tasks.delete-profile/handler
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:max-age (dt/duration {:hours 24})
|
:metrics (ig/ref :app.metrics/metrics)}
|
||||||
:metrics (ig/ref :app.metrics/metrics)}
|
|
||||||
|
|
||||||
:app.tasks.delete-object/handler
|
:app.tasks.file-media-gc/handler
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:metrics (ig/ref :app.metrics/metrics)}
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
|
:max-age (dt/duration {:hours 48})}
|
||||||
|
|
||||||
:app.tasks.delete-storage-object/handler
|
:app.tasks.file-xlog-gc/handler
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:storage (ig/ref :app.storage/storage)
|
:metrics (ig/ref :app.metrics/metrics)
|
||||||
:metrics (ig/ref :app.metrics/metrics)}
|
:max-age (dt/duration {:hours 48})}
|
||||||
|
|
||||||
:app.tasks.delete-profile/handler
|
:app.tasks.telemetry/handler
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:metrics (ig/ref :app.metrics/metrics)}
|
:version (:full cf/version)
|
||||||
|
:uri (cf/get :telemetry-uri)
|
||||||
|
:sprops (ig/ref :app.setup/props)}
|
||||||
|
|
||||||
:app.tasks.file-media-gc/handler
|
:app.srepl/server
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:port (cf/get :srepl-port)
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
:host (cf/get :srepl-host)}
|
||||||
:max-age (dt/duration {:hours 48})}
|
|
||||||
|
|
||||||
:app.tasks.file-xlog-gc/handler
|
:app.setup/props
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:pool (ig/ref :app.db/pool)}
|
||||||
:metrics (ig/ref :app.metrics/metrics)
|
|
||||||
:max-age (dt/duration {:hours 48})}
|
|
||||||
|
|
||||||
:app.tasks.telemetry/handler
|
:app.loggers.zmq/receiver
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:endpoint (cf/get :loggers-zmq-uri)}
|
||||||
:version (:full cfg/version)
|
|
||||||
:uri (:telemetry-uri config)
|
|
||||||
:sprops (ig/ref :app.setup/props)}
|
|
||||||
|
|
||||||
:app.srepl/server
|
:app.loggers.loki/reporter
|
||||||
{:port (:srepl-port config)
|
{:uri (cf/get :loggers-loki-uri)
|
||||||
:host (:srepl-host config)}
|
:receiver (ig/ref :app.loggers.zmq/receiver)
|
||||||
|
:executor (ig/ref :app.worker/executor)}
|
||||||
|
|
||||||
:app.setup/props
|
:app.loggers.mattermost/reporter
|
||||||
{:pool (ig/ref :app.db/pool)}
|
{:uri (cf/get :error-report-webhook)
|
||||||
|
:receiver (ig/ref :app.loggers.zmq/receiver)
|
||||||
|
:pool (ig/ref :app.db/pool)
|
||||||
|
:executor (ig/ref :app.worker/executor)}
|
||||||
|
|
||||||
:app.loggers.zmq/receiver
|
:app.loggers.mattermost/handler
|
||||||
{:endpoint (:loggers-zmq-uri config)}
|
{:pool (ig/ref :app.db/pool)}
|
||||||
|
|
||||||
:app.loggers.loki/reporter
|
:app.storage/storage
|
||||||
{:uri (:loggers-loki-uri config)
|
{:pool (ig/ref :app.db/pool)
|
||||||
:receiver (ig/ref :app.loggers.zmq/receiver)
|
:executor (ig/ref :app.worker/executor)
|
||||||
:executor (ig/ref :app.worker/executor)}
|
:backend (cf/get :storage-backend :fs)
|
||||||
|
:backends {:s3 (ig/ref [::main :app.storage.s3/backend])
|
||||||
|
:db (ig/ref [::main :app.storage.db/backend])
|
||||||
|
:fs (ig/ref [::main :app.storage.fs/backend])
|
||||||
|
:tmp (ig/ref [::tmp :app.storage.fs/backend])}}
|
||||||
|
|
||||||
:app.loggers.mattermost/reporter
|
[::main :app.storage.s3/backend]
|
||||||
{:uri (:error-report-webhook config)
|
{:region (cf/get :storage-s3-region)
|
||||||
:receiver (ig/ref :app.loggers.zmq/receiver)
|
:bucket (cf/get :storage-s3-bucket)}
|
||||||
:pool (ig/ref :app.db/pool)
|
|
||||||
:executor (ig/ref :app.worker/executor)}
|
|
||||||
|
|
||||||
:app.loggers.mattermost/handler
|
[::main :app.storage.fs/backend]
|
||||||
{:pool (ig/ref :app.db/pool)}
|
{:directory (cf/get :storage-fs-directory)}
|
||||||
|
|
||||||
:app.storage/storage
|
[::tmp :app.storage.fs/backend]
|
||||||
{:pool (ig/ref :app.db/pool)
|
{:directory "/tmp/penpot"}
|
||||||
:executor (ig/ref :app.worker/executor)
|
|
||||||
:backend (:storage-backend config :fs)
|
|
||||||
:backends {:s3 (ig/ref [::main :app.storage.s3/backend])
|
|
||||||
:db (ig/ref [::main :app.storage.db/backend])
|
|
||||||
:fs (ig/ref [::main :app.storage.fs/backend])
|
|
||||||
:tmp (ig/ref [::tmp :app.storage.fs/backend])}}
|
|
||||||
|
|
||||||
[::main :app.storage.s3/backend]
|
[::main :app.storage.db/backend]
|
||||||
{:region (:storage-s3-region config)
|
{:pool (ig/ref :app.db/pool)}})
|
||||||
:bucket (:storage-s3-bucket config)}
|
|
||||||
|
|
||||||
[::main :app.storage.fs/backend]
|
|
||||||
{:directory (:storage-fs-directory config)}
|
|
||||||
|
|
||||||
[::tmp :app.storage.fs/backend]
|
|
||||||
{:directory "/tmp/penpot"}
|
|
||||||
|
|
||||||
[::main :app.storage.db/backend]
|
|
||||||
{:pool (ig/ref :app.db/pool)}}
|
|
||||||
|
|
||||||
(when (:telemetry-server-enabled config)
|
|
||||||
{:app.telemetry/handler
|
|
||||||
{:pool (ig/ref :app.db/pool)
|
|
||||||
:executor (ig/ref :app.worker/executor)}
|
|
||||||
|
|
||||||
:app.telemetry/server
|
|
||||||
{:port (:telemetry-server-port config 6063)
|
|
||||||
:handler (ig/ref :app.telemetry/handler)
|
|
||||||
:name "telemetry"}})))
|
|
||||||
|
|
||||||
(defmethod ig/init-key :default [_ data] data)
|
(defmethod ig/init-key :default [_ data] data)
|
||||||
(defmethod ig/prep-key :default
|
(defmethod ig/prep-key :default
|
||||||
|
@ -364,15 +343,14 @@
|
||||||
|
|
||||||
(defn start
|
(defn start
|
||||||
[]
|
[]
|
||||||
(let [system-config (build-system-config cfg/config)]
|
(ig/load-namespaces system-config)
|
||||||
(ig/load-namespaces system-config)
|
(alter-var-root #'system (fn [sys]
|
||||||
(alter-var-root #'system (fn [sys]
|
(when sys (ig/halt! sys))
|
||||||
(when sys (ig/halt! sys))
|
(-> system-config
|
||||||
(-> system-config
|
(ig/prep)
|
||||||
(ig/prep)
|
(ig/init))))
|
||||||
(ig/init))))
|
(log/infof "welcome to penpot (version: '%s')"
|
||||||
(log/infof "welcome to penpot (version: '%s')"
|
(:full cf/version)))
|
||||||
(:full cfg/version))))
|
|
||||||
|
|
||||||
(defn stop
|
(defn stop
|
||||||
[]
|
[]
|
||||||
|
@ -380,14 +358,6 @@
|
||||||
(when sys (ig/halt! sys))
|
(when sys (ig/halt! sys))
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
(prefer-method print-method
|
|
||||||
clojure.lang.IRecord
|
|
||||||
clojure.lang.IDeref)
|
|
||||||
|
|
||||||
(prefer-method pprint/simple-dispatch
|
|
||||||
clojure.lang.IPersistentMap
|
|
||||||
clojure.lang.IDeref)
|
|
||||||
|
|
||||||
(defn -main
|
(defn -main
|
||||||
[& _args]
|
[& _args]
|
||||||
(start))
|
(start))
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.rpc.mutations.demo
|
(ns app.rpc.mutations.demo
|
||||||
"A demo specific mutations."
|
"A demo specific mutations."
|
||||||
|
@ -16,8 +16,8 @@
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.mutations.profile :as profile]
|
[app.rpc.mutations.profile :as profile]
|
||||||
[app.setup.initial-data :as sid]
|
[app.setup.initial-data :as sid]
|
||||||
[app.tasks :as tasks]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
|
[app.worker :as wrk]
|
||||||
[buddy.core.codecs :as bc]
|
[buddy.core.codecs :as bc]
|
||||||
[buddy.core.nonce :as bn]
|
[buddy.core.nonce :as bn]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
:password password
|
:password password
|
||||||
:props {:onboarding-viewed true}}]
|
:props {:onboarding-viewed true}}]
|
||||||
|
|
||||||
(when-not (:allow-demo-users cfg/config)
|
(when-not (cfg/get :allow-demo-users)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :demo-users-not-allowed
|
:code :demo-users-not-allowed
|
||||||
:hint "Demo users are disabled by config."))
|
:hint "Demo users are disabled by config."))
|
||||||
|
@ -51,9 +51,10 @@
|
||||||
(sid/load-initial-project! conn))
|
(sid/load-initial-project! conn))
|
||||||
|
|
||||||
;; Schedule deletion of the demo profile
|
;; Schedule deletion of the demo profile
|
||||||
(tasks/submit! conn {:name "delete-profile"
|
(wrk/submit! {::wrk/task :delete-profile
|
||||||
:delay cfg/deletion-delay
|
::wrk/delay cfg/deletion-delay
|
||||||
:props {:profile-id id}})
|
::wrk/conn conn
|
||||||
|
:profile-id id})
|
||||||
|
|
||||||
{:email email
|
{:email email
|
||||||
:password password})))
|
:password password})))
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.rpc.mutations.files
|
(ns app.rpc.mutations.files
|
||||||
(:require
|
(:require
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.permissions :as perms]
|
||||||
[app.rpc.queries.files :as files]
|
[app.rpc.queries.files :as files]
|
||||||
[app.rpc.queries.projects :as proj]
|
[app.rpc.queries.projects :as proj]
|
||||||
[app.tasks :as tasks]
|
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
[app.worker :as wrk]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
;; --- Helpers & Specs
|
;; --- Helpers & Specs
|
||||||
|
@ -126,9 +126,11 @@
|
||||||
(files/check-edition-permissions! conn profile-id id)
|
(files/check-edition-permissions! conn profile-id id)
|
||||||
|
|
||||||
;; Schedule object deletion
|
;; Schedule object deletion
|
||||||
(tasks/submit! conn {:name "delete-object"
|
(wrk/submit! {::wrk/task :delete-object
|
||||||
:delay cfg/deletion-delay
|
::wrk/delay cfg/deletion-delay
|
||||||
:props {:id id :type :file}})
|
::wrk/conn conn
|
||||||
|
:id id
|
||||||
|
:type :file})
|
||||||
|
|
||||||
(mark-file-deleted conn params)))
|
(mark-file-deleted conn params)))
|
||||||
|
|
||||||
|
|
|
@ -14,16 +14,16 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.emails :as emails]
|
[app.emails :as eml]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc.mutations.projects :as projects]
|
[app.rpc.mutations.projects :as projects]
|
||||||
[app.rpc.mutations.teams :as teams]
|
[app.rpc.mutations.teams :as teams]
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.queries.profile :as profile]
|
||||||
[app.setup.initial-data :as sid]
|
[app.setup.initial-data :as sid]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.tasks :as tasks]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
[app.worker :as wrk]
|
||||||
[buddy.hashers :as hashers]
|
[buddy.hashers :as hashers]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
@ -117,16 +117,19 @@
|
||||||
|
|
||||||
;; Don't allow proceed in register page if the email is
|
;; Don't allow proceed in register page if the email is
|
||||||
;; already reported as permanent bounced
|
;; already reported as permanent bounced
|
||||||
(when (emails/has-bounce-reports? conn (:email profile))
|
(when (eml/has-bounce-reports? conn (:email profile))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :email-has-permanent-bounces
|
:code :email-has-permanent-bounces
|
||||||
:hint "looks like the email has one or many bounces reported"))
|
:hint "looks like the email has one or many bounces reported"))
|
||||||
|
|
||||||
(emails/send! conn emails/register
|
(eml/send! {::eml/conn conn
|
||||||
{:to (:email profile)
|
::eml/factory eml/register
|
||||||
:name (:fullname profile)
|
:public-uri (:public-uri cfg)
|
||||||
:token vtoken
|
:to (:email profile)
|
||||||
:extra-data ptoken})
|
:name (:fullname profile)
|
||||||
|
:token vtoken
|
||||||
|
:extra-data ptoken})
|
||||||
|
|
||||||
(with-meta profile
|
(with-meta profile
|
||||||
{:before-complete (annotate-profile-register metrics profile)})))))
|
{:before-complete (annotate-profile-register metrics profile)})))))
|
||||||
|
|
||||||
|
@ -439,7 +442,7 @@
|
||||||
{:changed true})
|
{:changed true})
|
||||||
|
|
||||||
(defn- request-email-change
|
(defn- request-email-change
|
||||||
[{:keys [conn tokens]} {:keys [profile email] :as params}]
|
[{:keys [conn tokens] :as cfg} {:keys [profile email] :as params}]
|
||||||
(let [token (tokens :generate
|
(let [token (tokens :generate
|
||||||
{:iss :change-email
|
{:iss :change-email
|
||||||
:exp (dt/in-future "15m")
|
:exp (dt/in-future "15m")
|
||||||
|
@ -452,22 +455,24 @@
|
||||||
(when (not= email (:email profile))
|
(when (not= email (:email profile))
|
||||||
(check-profile-existence! conn params))
|
(check-profile-existence! conn params))
|
||||||
|
|
||||||
(when-not (emails/allow-send-emails? conn profile)
|
(when-not (eml/allow-send-emails? conn profile)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :profile-is-muted
|
:code :profile-is-muted
|
||||||
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces."))
|
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces."))
|
||||||
|
|
||||||
(when (emails/has-bounce-reports? conn email)
|
(when (eml/has-bounce-reports? conn email)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :email-has-permanent-bounces
|
:code :email-has-permanent-bounces
|
||||||
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
||||||
|
|
||||||
(emails/send! conn emails/change-email
|
(eml/send! {::eml/conn conn
|
||||||
{:to (:email profile)
|
::eml/factory eml/change-email
|
||||||
:name (:fullname profile)
|
:public-uri (:public-uri cfg)
|
||||||
:pending-email email
|
:to (:email profile)
|
||||||
:token token
|
:name (:fullname profile)
|
||||||
:extra-data ptoken})
|
:pending-email email
|
||||||
|
:token token
|
||||||
|
:extra-data ptoken})
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
|
||||||
|
@ -493,16 +498,18 @@
|
||||||
(let [ptoken (tokens :generate-predefined
|
(let [ptoken (tokens :generate-predefined
|
||||||
{:iss :profile-identity
|
{:iss :profile-identity
|
||||||
:profile-id (:id profile)})]
|
:profile-id (:id profile)})]
|
||||||
(emails/send! conn emails/password-recovery
|
(eml/send! {::eml/conn conn
|
||||||
{:to (:email profile)
|
::eml/factory eml/password-recovery
|
||||||
:token (:token profile)
|
:public-uri (:public-uri cfg)
|
||||||
:name (:fullname profile)
|
:to (:email profile)
|
||||||
:extra-data ptoken})
|
:token (:token profile)
|
||||||
|
:name (:fullname profile)
|
||||||
|
:extra-data ptoken})
|
||||||
nil))]
|
nil))]
|
||||||
|
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(when-let [profile (profile/retrieve-profile-data-by-email conn email)]
|
(when-let [profile (profile/retrieve-profile-data-by-email conn email)]
|
||||||
(when-not (emails/allow-send-emails? conn profile)
|
(when-not (eml/allow-send-emails? conn profile)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :profile-is-muted
|
:code :profile-is-muted
|
||||||
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces."))
|
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces."))
|
||||||
|
@ -512,7 +519,7 @@
|
||||||
:code :profile-not-verified
|
:code :profile-not-verified
|
||||||
:hint "the user need to validate profile before recover password"))
|
:hint "the user need to validate profile before recover password"))
|
||||||
|
|
||||||
(when (emails/has-bounce-reports? conn (:email profile))
|
(when (eml/has-bounce-reports? conn (:email profile))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :email-has-permanent-bounces
|
:code :email-has-permanent-bounces
|
||||||
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
||||||
|
@ -579,9 +586,10 @@
|
||||||
(check-can-delete-profile! conn profile-id)
|
(check-can-delete-profile! conn profile-id)
|
||||||
|
|
||||||
;; Schedule a complete deletion of profile
|
;; Schedule a complete deletion of profile
|
||||||
(tasks/submit! conn {:name "delete-profile"
|
(wrk/submit! {::wrk/task :delete-profile
|
||||||
:delay cfg/deletion-delay
|
::wrk/dalay cfg/deletion-delay
|
||||||
:props {:profile-id profile-id}})
|
::wrk/conn conn
|
||||||
|
:profile-id profile-id})
|
||||||
|
|
||||||
(db/update! conn :profile
|
(db/update! conn :profile
|
||||||
{:deleted-at (dt/now)}
|
{:deleted-at (dt/now)}
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.permissions :as perms]
|
||||||
[app.rpc.queries.projects :as proj]
|
[app.rpc.queries.projects :as proj]
|
||||||
[app.rpc.queries.teams :as teams]
|
[app.rpc.queries.teams :as teams]
|
||||||
[app.tasks :as tasks]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
[app.worker :as wrk]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
;; --- Helpers & Specs
|
;; --- Helpers & Specs
|
||||||
|
@ -128,9 +128,11 @@
|
||||||
(proj/check-edition-permissions! conn profile-id id)
|
(proj/check-edition-permissions! conn profile-id id)
|
||||||
|
|
||||||
;; Schedule object deletion
|
;; Schedule object deletion
|
||||||
(tasks/submit! conn {:name "delete-object"
|
(wrk/submit! {::wrk/task :delete-object
|
||||||
:delay cfg/deletion-delay
|
::wrk/delay cfg/deletion-delay
|
||||||
:props {:id id :type :project}})
|
::wrk/conn conn
|
||||||
|
:id id
|
||||||
|
:type :project})
|
||||||
|
|
||||||
(db/update! conn :project
|
(db/update! conn :project
|
||||||
{:deleted-at (dt/now)}
|
{:deleted-at (dt/now)}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.rpc.mutations.teams
|
(ns app.rpc.mutations.teams
|
||||||
(:require
|
(:require
|
||||||
|
@ -15,16 +15,16 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.emails :as emails]
|
[app.emails :as eml]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc.mutations.projects :as projects]
|
[app.rpc.mutations.projects :as projects]
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.permissions :as perms]
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.queries.profile :as profile]
|
||||||
[app.rpc.queries.teams :as teams]
|
[app.rpc.queries.teams :as teams]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.tasks :as tasks]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
[app.worker :as wrk]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[datoteka.core :as fs]))
|
[datoteka.core :as fs]))
|
||||||
|
|
||||||
|
@ -139,9 +139,11 @@
|
||||||
:code :only-owner-can-delete-team))
|
:code :only-owner-can-delete-team))
|
||||||
|
|
||||||
;; Schedule object deletion
|
;; Schedule object deletion
|
||||||
(tasks/submit! conn {:name "delete-object"
|
(wrk/submit! {::wrk/task :delete-object
|
||||||
:delay cfg/deletion-delay
|
::wrk/delay cfg/deletion-delay
|
||||||
:props {:id id :type :team}})
|
::wrk/conn conn
|
||||||
|
:id id
|
||||||
|
:type :team})
|
||||||
|
|
||||||
(db/update! conn :team
|
(db/update! conn :team
|
||||||
{:deleted-at (dt/now)}
|
{:deleted-at (dt/now)}
|
||||||
|
@ -323,27 +325,29 @@
|
||||||
:code :insufficient-permissions))
|
:code :insufficient-permissions))
|
||||||
|
|
||||||
;; First check if the current profile is allowed to send emails.
|
;; First check if the current profile is allowed to send emails.
|
||||||
(when-not (emails/allow-send-emails? conn profile)
|
(when-not (eml/allow-send-emails? conn profile)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :profile-is-muted
|
:code :profile-is-muted
|
||||||
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
||||||
|
|
||||||
(when (and member (not (emails/allow-send-emails? conn member)))
|
(when (and member (not (eml/allow-send-emails? conn member)))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :member-is-muted
|
:code :member-is-muted
|
||||||
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
||||||
|
|
||||||
;; Secondly check if the invited member email is part of the
|
;; Secondly check if the invited member email is part of the
|
||||||
;; global spam/bounce report.
|
;; global spam/bounce report.
|
||||||
(when (emails/has-bounce-reports? conn email)
|
(when (eml/has-bounce-reports? conn email)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :email-has-permanent-bounces
|
:code :email-has-permanent-bounces
|
||||||
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
|
||||||
|
|
||||||
(emails/send! conn emails/invite-to-team
|
(eml/send! {::eml/conn conn
|
||||||
{:to email
|
::eml/factory eml/invite-to-team
|
||||||
:invited-by (:fullname profile)
|
:public-uri (:public-uri cfg)
|
||||||
:team (:name team)
|
:to email
|
||||||
:token itoken
|
:invited-by (:fullname profile)
|
||||||
:extra-data ptoken})
|
:team (:name team)
|
||||||
|
:token itoken
|
||||||
|
:extra-data ptoken})
|
||||||
nil)))
|
nil)))
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
;; 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/.
|
|
||||||
;;
|
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.tasks
|
|
||||||
(:require
|
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.db :as db]
|
|
||||||
[app.metrics :as mtx]
|
|
||||||
[app.util.time :as dt]
|
|
||||||
[app.worker]
|
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[clojure.tools.logging :as log]
|
|
||||||
[integrant.core :as ig]))
|
|
||||||
|
|
||||||
(s/def ::name ::us/string)
|
|
||||||
(s/def ::delay
|
|
||||||
(s/or :int ::us/integer
|
|
||||||
:duration dt/duration?))
|
|
||||||
(s/def ::queue ::us/string)
|
|
||||||
|
|
||||||
(s/def ::task-options
|
|
||||||
(s/keys :req-un [::name]
|
|
||||||
:opt-un [::delay ::props ::queue]))
|
|
||||||
|
|
||||||
(def ^:private sql:insert-new-task
|
|
||||||
"insert into task (id, name, props, queue, priority, max_retries, scheduled_at)
|
|
||||||
values (?, ?, ?, ?, ?, ?, clock_timestamp() + ?)
|
|
||||||
returning id")
|
|
||||||
|
|
||||||
(defn submit!
|
|
||||||
[conn {:keys [name delay props queue priority max-retries]
|
|
||||||
:or {delay 0 props {} queue "default" priority 100 max-retries 3}
|
|
||||||
:as options}]
|
|
||||||
(us/verify ::task-options options)
|
|
||||||
(let [duration (dt/duration delay)
|
|
||||||
interval (db/interval duration)
|
|
||||||
props (db/tjson props)
|
|
||||||
id (uuid/next)]
|
|
||||||
(log/debugf "submit task '%s' to be executed in '%s'" name (str duration))
|
|
||||||
(db/exec-one! conn [sql:insert-new-task id name props queue priority max-retries interval])
|
|
||||||
id))
|
|
||||||
|
|
||||||
(defn- instrument!
|
|
||||||
[registry]
|
|
||||||
(mtx/instrument-vars!
|
|
||||||
[#'submit!]
|
|
||||||
{:registry registry
|
|
||||||
:type :counter
|
|
||||||
:labels ["name"]
|
|
||||||
:name "tasks_submit_total"
|
|
||||||
:help "A counter of task submissions."
|
|
||||||
:wrap (fn [rootf mobj]
|
|
||||||
(let [mdata (meta rootf)
|
|
||||||
origf (::original mdata rootf)]
|
|
||||||
(with-meta
|
|
||||||
(fn [conn params]
|
|
||||||
(let [tname (:name params)]
|
|
||||||
(mobj :inc [tname])
|
|
||||||
(origf conn params)))
|
|
||||||
{::original origf})))})
|
|
||||||
|
|
||||||
(mtx/instrument-vars!
|
|
||||||
[#'app.worker/run-task]
|
|
||||||
{:registry registry
|
|
||||||
:type :summary
|
|
||||||
:quantiles []
|
|
||||||
:name "tasks_checkout_timing"
|
|
||||||
:help "Latency measured between scheduld_at and execution time."
|
|
||||||
:wrap (fn [rootf mobj]
|
|
||||||
(let [mdata (meta rootf)
|
|
||||||
origf (::original mdata rootf)]
|
|
||||||
(with-meta
|
|
||||||
(fn [tasks item]
|
|
||||||
(let [now (inst-ms (dt/now))
|
|
||||||
sat (inst-ms (:scheduled-at item))]
|
|
||||||
(mobj :observe (- now sat))
|
|
||||||
(origf tasks item)))
|
|
||||||
{::original origf})))}))
|
|
||||||
|
|
||||||
;; --- STATE INIT: REGISTRY
|
|
||||||
|
|
||||||
(s/def ::tasks
|
|
||||||
(s/map-of keyword? fn?))
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::registry [_]
|
|
||||||
(s/keys :req-un [::mtx/metrics ::tasks]))
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::registry
|
|
||||||
[_ {:keys [metrics tasks]}]
|
|
||||||
(instrument! (:registry metrics))
|
|
||||||
(let [mobj (mtx/create
|
|
||||||
{:registry (:registry metrics)
|
|
||||||
:type :summary
|
|
||||||
:labels ["name"]
|
|
||||||
:quantiles []
|
|
||||||
:name "tasks_timing"
|
|
||||||
:help "Background task execution timing."})]
|
|
||||||
(reduce-kv (fn [res k v]
|
|
||||||
(let [tname (name k)]
|
|
||||||
(log/debugf "registring task '%s'" tname)
|
|
||||||
(assoc res tname (mtx/wrap-summary v mobj [tname]))))
|
|
||||||
{}
|
|
||||||
tasks)))
|
|
|
@ -5,7 +5,7 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.tasks.sendmail
|
(ns app.tasks.sendmail
|
||||||
(:require
|
(:require
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020 UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.tasks.telemetry
|
(ns app.tasks.telemetry
|
||||||
"A task that is reponsible to collect anonymous statistical
|
"A task that is reponsible to collect anonymous statistical
|
||||||
information about the current instance and send it to the telemetry
|
information about the current instance and send it to the telemetry
|
||||||
server."
|
server."
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
|
@ -32,7 +33,6 @@
|
||||||
(s/def ::sprops
|
(s/def ::sprops
|
||||||
(s/keys :req-un [::instance-id]))
|
(s/keys :req-un [::instance-id]))
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::handler [_]
|
(defmethod ig/pre-init-spec ::handler [_]
|
||||||
(s/keys :req-un [::db/pool ::version ::uri ::sprops]))
|
(s/keys :req-un [::db/pool ::version ::uri ::sprops]))
|
||||||
|
|
||||||
|
@ -128,11 +128,16 @@
|
||||||
|
|
||||||
(defn- retrieve-stats
|
(defn- retrieve-stats
|
||||||
[{:keys [conn version]}]
|
[{:keys [conn version]}]
|
||||||
(merge
|
(let [referer (if (cfg/get :telemetry-with-taiga)
|
||||||
{:version version
|
"taiga"
|
||||||
:with-taiga (:telemetry-with-taiga cfg/config false)
|
(cfg/get :telemetry-referer))]
|
||||||
:total-teams (retrieve-num-teams conn)
|
(-> {:version version
|
||||||
:total-projects (retrieve-num-projects conn)
|
:referer referer
|
||||||
:total-files (retrieve-num-files conn)}
|
:total-teams (retrieve-num-teams conn)
|
||||||
(retrieve-team-averages conn)
|
:total-projects (retrieve-num-projects conn)
|
||||||
(retrieve-jvm-stats)))
|
:total-files (retrieve-num-files conn)}
|
||||||
|
(d/merge
|
||||||
|
(retrieve-team-averages conn)
|
||||||
|
(retrieve-jvm-stats))
|
||||||
|
(d/without-nils))))
|
||||||
|
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
;; 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/.
|
|
||||||
;;
|
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2020-2021 UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.telemetry
|
|
||||||
(:require
|
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.db :as db]
|
|
||||||
[app.http.middleware :refer [wrap-parse-request-body]]
|
|
||||||
[clojure.pprint :refer [pprint]]
|
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[clojure.tools.logging :as log]
|
|
||||||
[integrant.core :as ig]
|
|
||||||
[promesa.exec :as px]
|
|
||||||
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
|
|
||||||
[ring.middleware.params :refer [wrap-params]]))
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; Migrations
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(def sql:create-instance-table
|
|
||||||
"CREATE TABLE IF NOT EXISTS telemetry.instance (
|
|
||||||
id uuid PRIMARY KEY,
|
|
||||||
created_at timestamptz NOT NULL DEFAULT now()
|
|
||||||
);")
|
|
||||||
|
|
||||||
(def sql:create-info-table
|
|
||||||
"CREATE TABLE telemetry.info (
|
|
||||||
instance_id uuid,
|
|
||||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
||||||
data jsonb NOT NULL,
|
|
||||||
|
|
||||||
PRIMARY KEY (instance_id, created_at)
|
|
||||||
) PARTITION BY RANGE(created_at);
|
|
||||||
|
|
||||||
CREATE TABLE telemetry.info_default (LIKE telemetry.info INCLUDING ALL);
|
|
||||||
|
|
||||||
ALTER TABLE telemetry.info
|
|
||||||
ATTACH PARTITION telemetry.info_default DEFAULT;")
|
|
||||||
|
|
||||||
(def migrations
|
|
||||||
[{:name "0001-add-telemetry-schema"
|
|
||||||
:fn #(db/exec! % ["CREATE SCHEMA IF NOT EXISTS telemetry;"])}
|
|
||||||
|
|
||||||
{:name "0002-add-instance-table"
|
|
||||||
:fn #(db/exec! % [sql:create-instance-table])}
|
|
||||||
|
|
||||||
{:name "0003-add-info-table"
|
|
||||||
:fn #(db/exec! % [sql:create-info-table])}
|
|
||||||
|
|
||||||
{:name "0004-del-instance-table"
|
|
||||||
:fn #(db/exec! % ["DROP TABLE telemetry.instance;"])}])
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::migrations [_ _] migrations)
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; Router Handler
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(declare handler)
|
|
||||||
(declare process-request)
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::handler
|
|
||||||
[_ cfg]
|
|
||||||
(-> (partial handler cfg)
|
|
||||||
(wrap-keyword-params)
|
|
||||||
(wrap-params)
|
|
||||||
(wrap-parse-request-body)))
|
|
||||||
|
|
||||||
(s/def ::instance-id ::us/uuid)
|
|
||||||
(s/def ::params (s/keys :req-un [::instance-id]))
|
|
||||||
|
|
||||||
(defn handler
|
|
||||||
[{:keys [executor] :as cfg} {:keys [params] :as request}]
|
|
||||||
(try
|
|
||||||
(let [params (us/conform ::params params)
|
|
||||||
cfg (assoc cfg
|
|
||||||
:instance-id (:instance-id params)
|
|
||||||
:data (dissoc params :instance-id))]
|
|
||||||
(px/run! executor (partial process-request cfg)))
|
|
||||||
(catch Exception e
|
|
||||||
;; We don't want notify user of a error, just log it for posible
|
|
||||||
;; future investigation.
|
|
||||||
(log/warn e (str "unexpected error on telemetry:\n"
|
|
||||||
(when-let [edata (ex-data e)]
|
|
||||||
(str "ex-data: \n"
|
|
||||||
(with-out-str (pprint edata))))
|
|
||||||
(str "params: \n"
|
|
||||||
(with-out-str (pprint params)))))))
|
|
||||||
{:status 200
|
|
||||||
:body "OK\n"})
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; Request Processing
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(def sql:insert-instance-info
|
|
||||||
"insert into telemetry.info (instance_id, data, created_at)
|
|
||||||
values (?, ?, date_trunc('day', now()))
|
|
||||||
on conflict (instance_id, created_at)
|
|
||||||
do update set data = ?")
|
|
||||||
|
|
||||||
(defn- process-request
|
|
||||||
[{:keys [pool instance-id data]}]
|
|
||||||
(try
|
|
||||||
(db/with-atomic [conn pool]
|
|
||||||
(let [data (db/json data)]
|
|
||||||
(db/exec! conn [sql:insert-instance-info
|
|
||||||
instance-id
|
|
||||||
data
|
|
||||||
data])))
|
|
||||||
(catch Exception e
|
|
||||||
(log/errorf e "error on procesing request"))))
|
|
|
@ -5,13 +5,13 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016-2020 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.util.blob
|
(ns app.util.blob
|
||||||
"A generic blob storage encoding. Mainly used for
|
"A generic blob storage encoding. Mainly used for page data, page
|
||||||
page data, page options and txlog payload storage."
|
options and txlog payload storage."
|
||||||
(:require
|
(:require
|
||||||
[app.config :as cfg]
|
[app.config :as cf]
|
||||||
[app.util.transit :as t]
|
[app.util.transit :as t]
|
||||||
[taoensso.nippy :as n])
|
[taoensso.nippy :as n])
|
||||||
(:import
|
(:import
|
||||||
|
@ -33,17 +33,15 @@
|
||||||
(declare encode-v2)
|
(declare encode-v2)
|
||||||
(declare encode-v3)
|
(declare encode-v3)
|
||||||
|
|
||||||
(def default-version
|
|
||||||
(:default-blob-version cfg/config 1))
|
|
||||||
|
|
||||||
(defn encode
|
(defn encode
|
||||||
([data] (encode data nil))
|
([data] (encode data nil))
|
||||||
([data {:keys [version] :or {version default-version}}]
|
([data {:keys [version]}]
|
||||||
(case (long version)
|
(let [version (or version (cf/get :default-blob-version 1))]
|
||||||
1 (encode-v1 data)
|
(case (long version)
|
||||||
2 (encode-v2 data)
|
1 (encode-v1 data)
|
||||||
3 (encode-v3 data)
|
2 (encode-v2 data)
|
||||||
(throw (ex-info "unsupported version" {:version version})))))
|
3 (encode-v3 data)
|
||||||
|
(throw (ex-info "unsupported version" {:version version}))))))
|
||||||
|
|
||||||
(defn decode
|
(defn decode
|
||||||
"A function used for decode persisted blobs in the database."
|
"A function used for decode persisted blobs in the database."
|
||||||
|
|
|
@ -60,7 +60,6 @@
|
||||||
[t1 t2]
|
[t1 t2]
|
||||||
(Duration/between t1 t2))
|
(Duration/between t1 t2))
|
||||||
|
|
||||||
|
|
||||||
(letfn [(conformer [v]
|
(letfn [(conformer [v]
|
||||||
(cond
|
(cond
|
||||||
(duration? v) v
|
(duration? v) v
|
||||||
|
|
|
@ -5,15 +5,17 @@
|
||||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; defined by the Mozilla Public License, v. 2.0.
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2020 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.worker
|
(ns app.worker
|
||||||
"Async tasks abstraction (impl)."
|
"Async tasks abstraction (impl)."
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
|
[app.metrics :as mtx]
|
||||||
[app.util.async :as aa]
|
[app.util.async :as aa]
|
||||||
[app.util.log4j :refer [update-thread-context!]]
|
[app.util.log4j :refer [update-thread-context!]]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
@ -35,21 +37,13 @@
|
||||||
;; Executor
|
;; Executor
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(s/def ::name ::us/string)
|
(s/def ::name keyword?)
|
||||||
(s/def ::min-threads ::us/integer)
|
(s/def ::min-threads ::us/integer)
|
||||||
(s/def ::max-threads ::us/integer)
|
(s/def ::max-threads ::us/integer)
|
||||||
(s/def ::idle-timeout ::us/integer)
|
(s/def ::idle-timeout ::us/integer)
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::executor [_]
|
(defmethod ig/pre-init-spec ::executor [_]
|
||||||
(s/keys :opt-un [::min-threads ::max-threads ::idle-timeout ::name]))
|
(s/keys :req-un [::min-threads ::max-threads ::idle-timeout ::name]))
|
||||||
|
|
||||||
(defmethod ig/prep-key ::executor
|
|
||||||
[_ cfg]
|
|
||||||
(merge {:min-threads 0
|
|
||||||
:max-threads 256
|
|
||||||
:idle-timeout 60000
|
|
||||||
:name "worker"}
|
|
||||||
cfg))
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::executor
|
(defmethod ig/init-key ::executor
|
||||||
[_ {:keys [min-threads max-threads idle-timeout name]}]
|
[_ {:keys [min-threads max-threads idle-timeout name]}]
|
||||||
|
@ -57,28 +51,29 @@
|
||||||
(int min-threads)
|
(int min-threads)
|
||||||
(int idle-timeout))
|
(int idle-timeout))
|
||||||
(.setStopTimeout 500)
|
(.setStopTimeout 500)
|
||||||
(.setName name)
|
(.setName (d/name name))
|
||||||
(.start)))
|
(.start)))
|
||||||
|
|
||||||
(defmethod ig/halt-key! ::executor
|
(defmethod ig/halt-key! ::executor
|
||||||
[_ instance]
|
[_ instance]
|
||||||
(.stop ^QueuedThreadPool instance))
|
(.stop ^QueuedThreadPool instance))
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Worker
|
;; Worker
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(declare event-loop-fn)
|
(declare event-loop-fn)
|
||||||
|
(declare instrument-tasks)
|
||||||
|
|
||||||
(s/def ::queue ::us/string)
|
(s/def ::queue keyword?)
|
||||||
(s/def ::parallelism ::us/integer)
|
(s/def ::parallelism ::us/integer)
|
||||||
(s/def ::batch-size ::us/integer)
|
(s/def ::batch-size ::us/integer)
|
||||||
(s/def ::tasks (s/map-of string? fn?))
|
(s/def ::tasks (s/map-of keyword? fn?))
|
||||||
(s/def ::poll-interval ::dt/duration)
|
(s/def ::poll-interval ::dt/duration)
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::worker [_]
|
(defmethod ig/pre-init-spec ::worker [_]
|
||||||
(s/keys :req-un [::executor
|
(s/keys :req-un [::executor
|
||||||
|
::mtx/metrics
|
||||||
::db/pool
|
::db/pool
|
||||||
::batch-size
|
::batch-size
|
||||||
::name
|
::name
|
||||||
|
@ -88,29 +83,29 @@
|
||||||
|
|
||||||
(defmethod ig/prep-key ::worker
|
(defmethod ig/prep-key ::worker
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(merge {:batch-size 2
|
(d/merge {:batch-size 2
|
||||||
:name "worker"
|
:name :worker
|
||||||
:poll-interval (dt/duration {:seconds 5})
|
:poll-interval (dt/duration {:seconds 5})
|
||||||
:queue "default"}
|
:queue :default}
|
||||||
cfg))
|
(d/without-nils cfg)))
|
||||||
|
|
||||||
(defmethod ig/init-key ::worker
|
(defmethod ig/init-key ::worker
|
||||||
[_ {:keys [pool poll-interval name queue] :as cfg}]
|
[_ {:keys [pool poll-interval name queue] :as cfg}]
|
||||||
(log/infof "starting worker '%s' on queue '%s'" name queue)
|
(log/infof "starting worker '%s' on queue '%s'" (d/name name) (d/name queue))
|
||||||
(let [cch (a/chan 1)
|
(let [close-ch (a/chan 1)
|
||||||
poll-ms (inst-ms poll-interval)]
|
poll-ms (inst-ms poll-interval)]
|
||||||
(a/go-loop []
|
(a/go-loop []
|
||||||
(let [[val port] (a/alts! [cch (event-loop-fn cfg)] :priority true)]
|
(let [[val port] (a/alts! [close-ch (event-loop-fn cfg)] :priority true)]
|
||||||
(cond
|
(cond
|
||||||
;; Terminate the loop if close channel is closed or
|
;; Terminate the loop if close channel is closed or
|
||||||
;; event-loop-fn returns nil.
|
;; event-loop-fn returns nil.
|
||||||
(or (= port cch) (nil? val))
|
(or (= port close-ch) (nil? val))
|
||||||
(log/infof "stop condition found; shutdown worker: '%s'" name)
|
(log/infof "stop condition found; shutdown worker: '%s'" (d/name name))
|
||||||
|
|
||||||
(db/pool-closed? pool)
|
(db/pool-closed? pool)
|
||||||
(do
|
(do
|
||||||
(log/info "worker eventloop is aborted because pool is closed")
|
(log/info "worker eventloop is aborted because pool is closed")
|
||||||
(a/close! cch))
|
(a/close! close-ch))
|
||||||
|
|
||||||
(and (instance? java.sql.SQLException val)
|
(and (instance? java.sql.SQLException val)
|
||||||
(contains? #{"08003" "08006" "08001" "08004"} (.getSQLState ^java.sql.SQLException val)))
|
(contains? #{"08003" "08006" "08001" "08004"} (.getSQLState ^java.sql.SQLException val)))
|
||||||
|
@ -143,13 +138,55 @@
|
||||||
(reify
|
(reify
|
||||||
java.lang.AutoCloseable
|
java.lang.AutoCloseable
|
||||||
(close [_]
|
(close [_]
|
||||||
(a/close! cch)))))
|
(a/close! close-ch)))))
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/halt-key! ::worker
|
(defmethod ig/halt-key! ::worker
|
||||||
[_ instance]
|
[_ instance]
|
||||||
(.close ^java.lang.AutoCloseable instance))
|
(.close ^java.lang.AutoCloseable instance))
|
||||||
|
|
||||||
|
;; --- SUBMIT
|
||||||
|
|
||||||
|
(s/def ::task keyword?)
|
||||||
|
(s/def ::delay (s/or :int ::us/integer :duration dt/duration?))
|
||||||
|
(s/def ::conn some?)
|
||||||
|
(s/def ::priority ::us/integer)
|
||||||
|
(s/def ::max-retries ::us/integer)
|
||||||
|
|
||||||
|
(s/def ::submit-options
|
||||||
|
(s/keys :req [::task ::conn]
|
||||||
|
:opt [::delay ::queue ::priority ::max-retries]))
|
||||||
|
|
||||||
|
(def ^:private sql:insert-new-task
|
||||||
|
"insert into task (id, name, props, queue, priority, max_retries, scheduled_at)
|
||||||
|
values (?, ?, ?, ?, ?, ?, clock_timestamp() + ?)
|
||||||
|
returning id")
|
||||||
|
|
||||||
|
(defn- extract-props
|
||||||
|
[options]
|
||||||
|
(persistent!
|
||||||
|
(reduce-kv (fn [res k v]
|
||||||
|
(cond-> res
|
||||||
|
(not (qualified-keyword? k))
|
||||||
|
(assoc! k v)))
|
||||||
|
(transient {})
|
||||||
|
options)))
|
||||||
|
|
||||||
|
(defn submit!
|
||||||
|
[{:keys [::task ::delay ::queue ::priority ::max-retries ::conn]
|
||||||
|
:or {delay 0 queue :default priority 100 max-retries 3}
|
||||||
|
:as options}]
|
||||||
|
(us/verify ::submit-options options)
|
||||||
|
(let [duration (dt/duration delay)
|
||||||
|
interval (db/interval duration)
|
||||||
|
props (-> options extract-props db/tjson)
|
||||||
|
id (uuid/next)]
|
||||||
|
(log/debugf "submit task '%s' to be executed in '%s'" (d/name task) (str duration))
|
||||||
|
(db/exec-one! conn [sql:insert-new-task id (d/name task) props (d/name queue) priority max-retries interval])
|
||||||
|
id))
|
||||||
|
|
||||||
|
;; --- RUNNER
|
||||||
|
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
sql:mark-as-retry
|
sql:mark-as-retry
|
||||||
|
@ -194,17 +231,18 @@
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defn- decode-task-row
|
(defn- decode-task-row
|
||||||
[{:keys [props] :as row}]
|
[{:keys [props name] :as row}]
|
||||||
(when row
|
(when row
|
||||||
(cond-> row
|
(cond-> row
|
||||||
(db/pgobject? props) (assoc :props (db/decode-transit-pgobject props)))))
|
(db/pgobject? props) (assoc :props (db/decode-transit-pgobject props))
|
||||||
|
(string? name) (assoc :name (keyword name)))))
|
||||||
|
|
||||||
(defn- handle-task
|
(defn- handle-task
|
||||||
[tasks {:keys [name] :as item}]
|
[tasks {:keys [name] :as item}]
|
||||||
(let [task-fn (get tasks name)]
|
(let [task-fn (get tasks name)]
|
||||||
(if task-fn
|
(if task-fn
|
||||||
(task-fn item)
|
(task-fn item)
|
||||||
(log/warnf "no task handler found for '%s'" (pr-str name)))
|
(log/warnf "no task handler found for '%s'" (d/name name)))
|
||||||
{:status :completed :task item}))
|
{:status :completed :task item}))
|
||||||
|
|
||||||
(defn get-error-context
|
(defn get-error-context
|
||||||
|
@ -236,13 +274,14 @@
|
||||||
|
|
||||||
(defn- run-task
|
(defn- run-task
|
||||||
[{:keys [tasks]} item]
|
[{:keys [tasks]} item]
|
||||||
(try
|
(let [name (d/name (:name item))]
|
||||||
(log/debugf "started task '%s/%s/%s'" (:name item) (:id item) (:retry-num item))
|
(try
|
||||||
(handle-task tasks item)
|
(log/debugf "started task '%s/%s/%s'" name (:id item) (:retry-num item))
|
||||||
(catch Exception e
|
(handle-task tasks item)
|
||||||
(handle-exception e item))
|
(catch Exception e
|
||||||
(finally
|
(handle-exception e item))
|
||||||
(log/debugf "finished task '%s/%s/%s'" (:name item) (:id item) (:retry-num item)))))
|
(finally
|
||||||
|
(log/debugf "finished task '%s/%s/%s'" name (:id item) (:retry-num item))))))
|
||||||
|
|
||||||
(def sql:select-next-tasks
|
(def sql:select-next-tasks
|
||||||
"select * from task as t
|
"select * from task as t
|
||||||
|
@ -256,7 +295,7 @@
|
||||||
(defn- event-loop-fn*
|
(defn- event-loop-fn*
|
||||||
[{:keys [pool executor batch-size] :as cfg}]
|
[{:keys [pool executor batch-size] :as cfg}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [queue (:queue cfg)
|
(let [queue (name (:queue cfg))
|
||||||
items (->> (db/exec! conn [sql:select-next-tasks queue batch-size])
|
items (->> (db/exec! conn [sql:select-next-tasks queue batch-size])
|
||||||
(map decode-task-row)
|
(map decode-task-row)
|
||||||
(seq))
|
(seq))
|
||||||
|
@ -288,16 +327,16 @@
|
||||||
(declare synchronize-schedule)
|
(declare synchronize-schedule)
|
||||||
|
|
||||||
(s/def ::fn (s/or :var var? :fn fn?))
|
(s/def ::fn (s/or :var var? :fn fn?))
|
||||||
(s/def ::id ::us/string)
|
(s/def ::id keyword?)
|
||||||
(s/def ::cron dt/cron?)
|
(s/def ::cron dt/cron?)
|
||||||
(s/def ::props (s/nilable map?))
|
(s/def ::props (s/nilable map?))
|
||||||
(s/def ::task keyword?)
|
(s/def ::task keyword?)
|
||||||
|
|
||||||
(s/def ::scheduled-task-spec
|
(s/def ::scheduled-task
|
||||||
(s/keys :req-un [::id ::cron ::task]
|
(s/keys :req-un [::cron ::task]
|
||||||
:opt-un [::props]))
|
:opt-un [::props ::id]))
|
||||||
|
|
||||||
(s/def ::schedule (s/coll-of (s/nilable ::scheduled-task-spec)))
|
(s/def ::schedule (s/coll-of (s/nilable ::scheduled-task)))
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::scheduler [_]
|
(defmethod ig/pre-init-spec ::scheduler [_]
|
||||||
(s/keys :req-un [::executor ::db/pool ::schedule ::tasks]))
|
(s/keys :req-un [::executor ::db/pool ::schedule ::tasks]))
|
||||||
|
@ -307,8 +346,13 @@
|
||||||
(let [scheduler (Executors/newScheduledThreadPool (int 1))
|
(let [scheduler (Executors/newScheduledThreadPool (int 1))
|
||||||
schedule (->> schedule
|
schedule (->> schedule
|
||||||
(filter some?)
|
(filter some?)
|
||||||
|
;; If id is not defined, use the task as id.
|
||||||
|
(map (fn [{:keys [id task] :as item}]
|
||||||
|
(if (some? id)
|
||||||
|
item
|
||||||
|
(assoc item :id task))))
|
||||||
(map (fn [{:keys [task] :as item}]
|
(map (fn [{:keys [task] :as item}]
|
||||||
(let [f (get tasks (name task))]
|
(let [f (get tasks task)]
|
||||||
(when-not f
|
(when-not f
|
||||||
(ex/raise :type :internal
|
(ex/raise :type :internal
|
||||||
:code :task-not-found
|
:code :task-not-found
|
||||||
|
@ -341,7 +385,8 @@
|
||||||
|
|
||||||
(defn- synchronize-schedule-item
|
(defn- synchronize-schedule-item
|
||||||
[conn {:keys [id cron]}]
|
[conn {:keys [id cron]}]
|
||||||
(let [cron (str cron)]
|
(let [cron (str cron)
|
||||||
|
id (name id)]
|
||||||
(log/infof "initialize scheduled task '%s' (cron: '%s')" id cron)
|
(log/infof "initialize scheduled task '%s' (cron: '%s')" id cron)
|
||||||
(db/exec-one! conn [sql:upsert-scheduled-task id cron cron])))
|
(db/exec-one! conn [sql:upsert-scheduled-task id cron cron])))
|
||||||
|
|
||||||
|
@ -390,3 +435,62 @@
|
||||||
[{:keys [scheduler] :as cfg} {:keys [cron] :as task}]
|
[{:keys [scheduler] :as cfg} {:keys [cron] :as task}]
|
||||||
(let [ms (ms-until-valid cron)]
|
(let [ms (ms-until-valid cron)]
|
||||||
(px/schedule! scheduler ms (partial execute-scheduled-task cfg task))))
|
(px/schedule! scheduler ms (partial execute-scheduled-task cfg task))))
|
||||||
|
|
||||||
|
;; --- INSTRUMENTATION
|
||||||
|
|
||||||
|
(defn instrument!
|
||||||
|
[registry]
|
||||||
|
(mtx/instrument-vars!
|
||||||
|
[#'submit!]
|
||||||
|
{:registry registry
|
||||||
|
:type :counter
|
||||||
|
:labels ["name"]
|
||||||
|
:name "tasks_submit_total"
|
||||||
|
:help "A counter of task submissions."
|
||||||
|
:wrap (fn [rootf mobj]
|
||||||
|
(let [mdata (meta rootf)
|
||||||
|
origf (::original mdata rootf)]
|
||||||
|
(with-meta
|
||||||
|
(fn [conn params]
|
||||||
|
(let [tname (:name params)]
|
||||||
|
(mobj :inc [tname])
|
||||||
|
(origf conn params)))
|
||||||
|
{::original origf})))})
|
||||||
|
|
||||||
|
(mtx/instrument-vars!
|
||||||
|
[#'app.worker/run-task]
|
||||||
|
{:registry registry
|
||||||
|
:type :summary
|
||||||
|
:quantiles []
|
||||||
|
:name "tasks_checkout_timing"
|
||||||
|
:help "Latency measured between scheduld_at and execution time."
|
||||||
|
:wrap (fn [rootf mobj]
|
||||||
|
(let [mdata (meta rootf)
|
||||||
|
origf (::original mdata rootf)]
|
||||||
|
(with-meta
|
||||||
|
(fn [tasks item]
|
||||||
|
(let [now (inst-ms (dt/now))
|
||||||
|
sat (inst-ms (:scheduled-at item))]
|
||||||
|
(mobj :observe (- now sat))
|
||||||
|
(origf tasks item)))
|
||||||
|
{::original origf})))}))
|
||||||
|
|
||||||
|
|
||||||
|
(defmethod ig/pre-init-spec ::registry [_]
|
||||||
|
(s/keys :req-un [::mtx/metrics ::tasks]))
|
||||||
|
|
||||||
|
(defmethod ig/init-key ::registry
|
||||||
|
[_ {:keys [metrics tasks]}]
|
||||||
|
(let [mobj (mtx/create
|
||||||
|
{:registry (:registry metrics)
|
||||||
|
:type :summary
|
||||||
|
:labels ["name"]
|
||||||
|
:quantiles []
|
||||||
|
:name "tasks_timing"
|
||||||
|
:help "Background task execution timing."})]
|
||||||
|
(reduce-kv (fn [res k v]
|
||||||
|
(let [tname (name k)]
|
||||||
|
(log/debugf "registring task '%s'" tname)
|
||||||
|
(assoc res k (mtx/wrap-summary v mobj [tname]))))
|
||||||
|
{}
|
||||||
|
tasks)))
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cfg]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.main :as main]
|
[app.main :as main]
|
||||||
[app.media]
|
[app.media]
|
||||||
|
@ -38,16 +38,12 @@
|
||||||
(def ^:dynamic *system* nil)
|
(def ^:dynamic *system* nil)
|
||||||
(def ^:dynamic *pool* nil)
|
(def ^:dynamic *pool* nil)
|
||||||
|
|
||||||
(def config
|
|
||||||
(merge {:redis-uri "redis://redis/1"
|
|
||||||
:database-uri "postgresql://postgres/penpot_test"
|
|
||||||
:storage-fs-directory "/tmp/app/storage"
|
|
||||||
:migrations-verbose false}
|
|
||||||
cfg/config))
|
|
||||||
|
|
||||||
(defn state-init
|
(defn state-init
|
||||||
[next]
|
[next]
|
||||||
(let [config (-> (main/build-system-config config)
|
(let [config (-> main/system-config
|
||||||
|
(assoc-in [:app.msgbus/msgbus :redis-uri] "redis://redis/1")
|
||||||
|
(assoc-in [:app.db/pool :uri] "postgresql://postgres/penpot_test")
|
||||||
|
(assoc-in [[:app.main/main :app.storage.fs/backend] :directory] "/tmp/app/storage")
|
||||||
(dissoc :app.srepl/server
|
(dissoc :app.srepl/server
|
||||||
:app.http/server
|
:app.http/server
|
||||||
:app.http/router
|
:app.http/router
|
||||||
|
@ -328,8 +324,10 @@
|
||||||
"Helper for mock app.config/get"
|
"Helper for mock app.config/get"
|
||||||
[data]
|
[data]
|
||||||
(fn
|
(fn
|
||||||
([key] (get (merge config data) key))
|
([key]
|
||||||
([key default] (get (merge config data) key default))))
|
(get data key (cf/get key)))
|
||||||
|
([key default]
|
||||||
|
(get data key (cf/get key default)))))
|
||||||
|
|
||||||
(defn reset-mock!
|
(defn reset-mock!
|
||||||
[m]
|
[m]
|
||||||
|
|
|
@ -401,6 +401,9 @@
|
||||||
(keyword? maybe-keyword)
|
(keyword? maybe-keyword)
|
||||||
(core/name maybe-keyword)
|
(core/name maybe-keyword)
|
||||||
|
|
||||||
|
(string? maybe-keyword)
|
||||||
|
maybe-keyword
|
||||||
|
|
||||||
(nil? maybe-keyword) default-value
|
(nil? maybe-keyword) default-value
|
||||||
|
|
||||||
:else
|
:else
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; 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/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns app.common.exceptions
|
(ns app.common.exceptions
|
||||||
"A helpers for work with exceptions."
|
"A helpers for work with exceptions."
|
||||||
|
|
Loading…
Add table
Reference in a new issue