🎉 Add html emails.

This commit is contained in:
Andrey Antukh 2020-06-01 13:19:35 +02:00 committed by Alonso Torres
parent 721879aaa8
commit fbd6e395a4
27 changed files with 368 additions and 366 deletions

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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)}))]

View file

@ -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

View file

@ -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

View file

@ -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