| + {% block content %} + {% endblock %} + | +
diff --git a/backend/deps.edn b/backend/deps.edn index 9a43a21467..bf334bc380 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -19,8 +19,9 @@ io.prometheus/simpleclient_hotspot {:mvn/version "0.9.0"} io.prometheus/simpleclient_httpserver {:mvn/version "0.9.0"} + selmer/selmer {:mvn/version "1.12.18"} + expound/expound {:mvn/version "0.8.4"} - instaparse/instaparse {:mvn/version "1.4.10"} com.cognitect/transit-clj {:mvn/version "1.0.324"} io.lettuce/lettuce-core {:mvn/version "5.2.2.RELEASE"} diff --git a/backend/resources/emails/base.html b/backend/resources/emails/base.html new file mode 100644 index 0000000000..d399ce6c4b --- /dev/null +++ b/backend/resources/emails/base.html @@ -0,0 +1,85 @@ + + +
+ + + {% block head %} +| + |
+
+
+
+
+
+
+
+
+
+
|
+ + |
| + |
+
+
+
+
|
+ + |
Hello {{name}}!
+ +We received a request to change your current email to {{ pending-email }}.
+ +Click to the link below to confirm the change:
+ + Confirm email change + +If you received this email by mistake, please consider changing your password + for security reasons.
+ +Enjoy!
+ +The UXBOX team.
+ +{% endblock %} diff --git a/backend/resources/emails/change-email/en.subj b/backend/resources/emails/change-email/en.subj new file mode 100644 index 0000000000..fae232cbdb --- /dev/null +++ b/backend/resources/emails/change-email/en.subj @@ -0,0 +1 @@ +Email change \ No newline at end of file diff --git a/backend/resources/emails/change-email/en.mustache b/backend/resources/emails/change-email/en.txt similarity index 66% rename from backend/resources/emails/change-email/en.mustache rename to backend/resources/emails/change-email/en.txt index 93ed9a5772..f4232aac44 100644 --- a/backend/resources/emails/change-email/en.mustache +++ b/backend/resources/emails/change-email/en.txt @@ -1,19 +1,13 @@ --- begin :subject -Email change. --- end - --- begin :body-text Hello {{name}}! -We received a request to change your current email to {{ pendingEmail }}. +We received a request to change your current email to {{ pending-email }}. Click to the link below to confirm the change: -{{ publicUri }}/#/auth/verify-token?token={{token}} +{{ public-uri }}/#/auth/verify-token?token={{token}} If you received this email by mistake, please consider changing your password for security reasons. Enjoy! The UXBOX team. --- end diff --git a/backend/resources/emails/debug-email-list.html b/backend/resources/emails/debug-email-list.html deleted file mode 100644 index b2827e29e2..0000000000 --- a/backend/resources/emails/debug-email-list.html +++ /dev/null @@ -1,14 +0,0 @@ - - -| - |
-
-
-
|
- - |
Hello {{name}}!
+ +We received a request to reset your password. Click the link + below to choose a new one:
+ + + Reset password. + + ++ If you received this email by mistake, you can safely ignore + it. Your password won't be changed. +
+ +Enjoy!
+ +The UXBOX team.
+ +{% endblock %} diff --git a/backend/resources/emails/password-recovery/en.subj b/backend/resources/emails/password-recovery/en.subj new file mode 100644 index 0000000000..bca0a5e0b3 --- /dev/null +++ b/backend/resources/emails/password-recovery/en.subj @@ -0,0 +1 @@ +Password reset \ No newline at end of file diff --git a/backend/resources/emails/password-recovery/en.mustache b/backend/resources/emails/password-recovery/en.txt similarity index 66% rename from backend/resources/emails/password-recovery/en.mustache rename to backend/resources/emails/password-recovery/en.txt index f419e46a60..cbd00e35b8 100644 --- a/backend/resources/emails/password-recovery/en.mustache +++ b/backend/resources/emails/password-recovery/en.txt @@ -1,18 +1,12 @@ --- begin :subject -Password reset. --- end - --- begin :body-text Hello {{name}}! We received a request to reset your password. Click the link below to choose a new one: -{{ publicUri }}/#/auth/recovery?token={{token}} +{{ public-uri }}/#/auth/recovery?token={{token}} If you received this email by mistake, you can safely ignore it. Your password won't be changed. Enjoy! The UXBOX team. --- end diff --git a/backend/resources/emails/register/en.html b/backend/resources/emails/register/en.html new file mode 100644 index 0000000000..a7dd1d9acb --- /dev/null +++ b/backend/resources/emails/register/en.html @@ -0,0 +1,20 @@ +{% extends "emails/base.html" %} + +{% block content %} +Hello {{name}}!
+ ++ Thanks for signing up for your UXBOX account! Please verify your + email using the link below adn get started building mockups and + prototypes today! +
+ + + Verify token + + +Enjoy!
+ +The UXBOX team.
+ +{% endblock %} diff --git a/backend/resources/emails/register/en.subj b/backend/resources/emails/register/en.subj new file mode 100644 index 0000000000..4b49d94ba3 --- /dev/null +++ b/backend/resources/emails/register/en.subj @@ -0,0 +1 @@ +Verify email. diff --git a/backend/resources/emails/register/en.mustache b/backend/resources/emails/register/en.txt similarity index 61% rename from backend/resources/emails/register/en.mustache rename to backend/resources/emails/register/en.txt index ff63fd0b78..70efbfe173 100644 --- a/backend/resources/emails/register/en.mustache +++ b/backend/resources/emails/register/en.txt @@ -1,15 +1,9 @@ --- begin :subject -Verify email. --- end - --- begin :body-text Hello {{name}}! Thanks for signing up for your UXBOX account! Please verify your email using the link below adn get started building mockups and prototypes today! -{{ publicUri }}/#/auth/verify-token?token={{token}} +{{ public-uri }}/#/auth/verify-token?token={{token}} Enjoy! The UXBOX team. --- end \ No newline at end of file diff --git a/backend/resources/emails/register/fr.mustache b/backend/resources/emails/register/fr.mustache deleted file mode 100644 index b4a5301490..0000000000 --- a/backend/resources/emails/register/fr.mustache +++ /dev/null @@ -1,17 +0,0 @@ --- begin :subject -Bienvenue sur UXBOX. --- end - --- begin :body-text -Bonjour {{user}}! - -Bienvenue sur UXBOX. - -L'équipe UXBOX. --- end - --- begin :body-html -Bonjour {{user}} !
-Bienvenue sur UXBOX.
-L'équipe UXBOX.
--- end \ No newline at end of file diff --git a/backend/resources/public/static/images/email/instagram.png b/backend/resources/public/static/images/email/instagram.png new file mode 100644 index 0000000000..107e25ebc1 Binary files /dev/null and b/backend/resources/public/static/images/email/instagram.png differ diff --git a/backend/resources/public/static/images/email/taiga.png b/backend/resources/public/static/images/email/taiga.png new file mode 100644 index 0000000000..d1ae46f5dc Binary files /dev/null and b/backend/resources/public/static/images/email/taiga.png differ diff --git a/backend/resources/public/static/images/email/uxbox.png b/backend/resources/public/static/images/email/uxbox.png new file mode 100644 index 0000000000..ebdf689f05 Binary files /dev/null and b/backend/resources/public/static/images/email/uxbox.png differ diff --git a/backend/src/uxbox/config.clj b/backend/src/uxbox/config.clj index a500b1f87d..ac541b922f 100644 --- a/backend/src/uxbox/config.clj +++ b/backend/src/uxbox/config.clj @@ -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" diff --git a/backend/src/uxbox/emails.clj b/backend/src/uxbox/emails.clj index 28077838c8..5b48d5eb13 100644 --- a/backend/src/uxbox/emails.clj +++ b/backend/src/uxbox/emails.clj @@ -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 diff --git a/backend/src/uxbox/migrations.clj b/backend/src/uxbox/migrations.clj index a8b5415a3a..bc4c4d0ad7 100644 --- a/backend/src/uxbox/migrations.clj +++ b/backend/src/uxbox/migrations.clj @@ -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" diff --git a/backend/src/uxbox/services/mutations/profile.clj b/backend/src/uxbox/services/mutations/profile.clj index d2b43537aa..17c8ebc768 100644 --- a/backend/src/uxbox/services/mutations/profile.clj +++ b/backend/src/uxbox/services/mutations/profile.clj @@ -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)}))] diff --git a/backend/src/uxbox/tasks.clj b/backend/src/uxbox/tasks.clj index 4a320c5535..c46ac1c0b6 100644 --- a/backend/src/uxbox/tasks.clj +++ b/backend/src/uxbox/tasks.clj @@ -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 diff --git a/backend/src/uxbox/util/emails.clj b/backend/src/uxbox/util/emails.clj index 2540859c24..382d48df8e 100644 --- a/backend/src/uxbox/util/emails.clj +++ b/backend/src/uxbox/util/emails.clj @@ -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 diff --git a/backend/src/uxbox/util/template.clj b/backend/src/uxbox/util/template.clj index 4260682bd4..becb12911f 100644 --- a/backend/src/uxbox/util/template.clj +++ b/backend/src/uxbox/util/template.clj @@ -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