mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 05:56:38 +02:00
🎉 Add html emails.
This commit is contained in:
parent
721879aaa8
commit
fbd6e395a4
27 changed files with 368 additions and 366 deletions
|
@ -32,8 +32,8 @@
|
|||
:redis-uri "redis://redis/0"
|
||||
:media-directory "resources/public/media"
|
||||
:assets-directory "resources/public/static"
|
||||
:media-uri "http://localhost:6060/media/"
|
||||
:assets-uri "http://localhost:6060/static/"
|
||||
:media-uri "http://localhost:6060/media"
|
||||
:assets-uri "http://localhost:6060/static"
|
||||
|
||||
:sendmail-backend "console"
|
||||
:sendmail-reply-to "no-reply@example.com"
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
|
||||
(defn default-context
|
||||
[]
|
||||
{:static media/resolve-asset
|
||||
:comment (constantly nil)
|
||||
{:assets-uri (:assets-uri cfg/config)
|
||||
:public-uri (:public-uri cfg/config)})
|
||||
|
||||
;; --- Public API
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
[mount.core :as mount :refer [defstate]]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.util.migrations :as mg]
|
||||
[uxbox.util.template :as tmpl]))
|
||||
[uxbox.util.migrations :as mg]))
|
||||
|
||||
(def +migrations+
|
||||
{:name "uxbox-main"
|
||||
|
|
|
@ -90,7 +90,6 @@
|
|||
(emails/send! conn emails/register
|
||||
{:to (:email profile)
|
||||
:name (:fullname profile)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:token token})
|
||||
profile)))
|
||||
|
||||
|
@ -339,7 +338,6 @@
|
|||
(emails/send! conn emails/change-email
|
||||
{:to (:email profile)
|
||||
:name (:fullname profile)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:pending-email email
|
||||
:token token})
|
||||
nil)))
|
||||
|
@ -430,7 +428,6 @@
|
|||
(send-email-notification [conn profile]
|
||||
(emails/send! conn emails/password-recovery
|
||||
{:to (:email profile)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:token (:token profile)
|
||||
:name (:fullname profile)}))]
|
||||
|
||||
|
|
|
@ -52,15 +52,15 @@
|
|||
:cron (dt/cron "1 1 */1 * * ? *")
|
||||
:fn #'uxbox.tasks.gc/remove-media}])
|
||||
|
||||
(defstate worker
|
||||
(defstate tasks-worker
|
||||
:start (impl/start-worker! {:tasks tasks
|
||||
:xtor scheduler})
|
||||
:stop (impl/stop! worker))
|
||||
:stop (impl/stop! tasks-worker))
|
||||
|
||||
(defstate scheduler-worker
|
||||
:start (impl/start-scheduler-worker! {:schedule schedule
|
||||
:xtor scheduler})
|
||||
:stop (impl/stop! worker))
|
||||
:stop (impl/stop! scheduler-worker))
|
||||
|
||||
;; --- Public API
|
||||
|
||||
|
|
|
@ -9,48 +9,13 @@
|
|||
[clojure.java.io :as io]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[instaparse.core :as insta]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.util.template :as tmpl]))
|
||||
|
||||
;; --- Impl.
|
||||
|
||||
(def ^:private grammar
|
||||
(str "message = part*"
|
||||
"part = begin header body end; "
|
||||
"header = tag* eol; "
|
||||
"tag = space keyword; "
|
||||
"body = line*; "
|
||||
"begin = #'--\\s+begin\\s+'; "
|
||||
"end = #'--\\s+end\\s*' eol*; "
|
||||
"keyword = #':[\\w\\-]+'; "
|
||||
"space = #'\\s*'; "
|
||||
"line = #'.*\\n'; "
|
||||
"eol = ('\\n' | '\\r\\n'); "))
|
||||
|
||||
(def ^:private parse-fn (insta/parser grammar))
|
||||
(def ^:private email-path "emails/%(id)s/%(lang)s.mustache")
|
||||
|
||||
(defn- parse-template
|
||||
[content]
|
||||
(loop [state {}
|
||||
parts (drop 1 (parse-fn content))]
|
||||
(if-let [[_ _ header body] (first parts)]
|
||||
(let [type (get-in header [1 2 1])
|
||||
type (keyword (str/slice type 1))
|
||||
content (apply str (map second (rest body)))]
|
||||
(recur (assoc state type (str/trim content " \n"))
|
||||
(rest parts)))
|
||||
state)))
|
||||
|
||||
(s/def ::subject string?)
|
||||
(s/def ::body-text string?)
|
||||
(s/def ::body-html string?)
|
||||
|
||||
(s/def ::parsed-email
|
||||
(s/keys :req-un [::subject ::body-text]
|
||||
:opt-un [::body-html]))
|
||||
(def ^:private email-path "emails/%(id)s/%(lang)s.%(type)s")
|
||||
|
||||
(defn- build-base-email
|
||||
[data context]
|
||||
|
@ -66,13 +31,28 @@
|
|||
(:body-html data) (conj {:type "text/html"
|
||||
:value (:body-html data)}))})
|
||||
|
||||
(defn- render-email-part
|
||||
[type id context]
|
||||
(let [lang (:lang context :en)
|
||||
path (str/format email-path {:id (name id)
|
||||
:lang (name lang)
|
||||
:type (name type)})]
|
||||
(some-> (io/resource path)
|
||||
(tmpl/render context))))
|
||||
|
||||
(defn- impl-build-email
|
||||
[id context]
|
||||
(let [lang (:lang context :en)
|
||||
path (str/format email-path {:id (name id) :lang (name lang)})]
|
||||
(-> (tmpl/render path context)
|
||||
(parse-template)
|
||||
(build-base-email context))))
|
||||
subj (render-email-part :subj id context)
|
||||
html (render-email-part :html id context)
|
||||
text (render-email-part :txt id context)]
|
||||
|
||||
{:subject subj
|
||||
:content (cond-> []
|
||||
text (conj {:type "text/plain"
|
||||
:value text})
|
||||
html (conj {:type "text/html"
|
||||
:value html}))}))
|
||||
|
||||
;; --- Public API
|
||||
|
||||
|
|
|
@ -12,57 +12,24 @@
|
|||
[clojure.walk :as walk]
|
||||
[clojure.java.io :as io]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.common.exceptions :as ex])
|
||||
(:import
|
||||
java.io.StringReader
|
||||
java.util.HashMap
|
||||
java.util.function.Function;
|
||||
com.github.mustachejava.DefaultMustacheFactory
|
||||
com.github.mustachejava.Mustache))
|
||||
|
||||
(def ^DefaultMustacheFactory +mustache-factory+ (DefaultMustacheFactory.))
|
||||
|
||||
(defn- adapt-context
|
||||
[data]
|
||||
(walk/postwalk (fn [x]
|
||||
(cond
|
||||
(instance? clojure.lang.Named x)
|
||||
(str/camel (name x))
|
||||
|
||||
(instance? clojure.lang.MapEntry x)
|
||||
x
|
||||
|
||||
(fn? x)
|
||||
(reify Function
|
||||
(apply [this content]
|
||||
(try
|
||||
(x content)
|
||||
(catch Exception e
|
||||
(log/error e "Error on executing" x)
|
||||
""))))
|
||||
|
||||
(or (vector? x) (list? x))
|
||||
(java.util.ArrayList. ^java.util.List x)
|
||||
|
||||
(map? x)
|
||||
(java.util.HashMap. ^java.util.Map x)
|
||||
|
||||
(set? x)
|
||||
(java.util.HashSet. ^java.util.Set x)
|
||||
|
||||
:else
|
||||
x))
|
||||
data))
|
||||
[selmer.parser :as sp]
|
||||
[uxbox.common.exceptions :as ex]))
|
||||
|
||||
;; (sp/cache-off!)
|
||||
|
||||
(defn render
|
||||
[path context]
|
||||
(try
|
||||
(let [context (adapt-context context)
|
||||
template (.compile +mustache-factory+ path)]
|
||||
(with-out-str
|
||||
(let [scope (HashMap. ^java.util.Map (walk/stringify-keys context))]
|
||||
(.execute ^Mustache template *out* scope))))
|
||||
(sp/render-file path context)
|
||||
(catch Exception cause
|
||||
(ex/raise :type :internal
|
||||
:code :template-render-error
|
||||
:cause cause))))
|
||||
|
||||
(defn render-string
|
||||
[content context]
|
||||
(try
|
||||
(sp/render content context)
|
||||
(catch Exception cause
|
||||
(ex/raise :type :internal
|
||||
:code :template-render-error
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue