can send emails on a whim

This commit is contained in:
Bryce Covert
2018-04-13 20:28:31 -07:00
parent c8e959bd40
commit 431c2883e2
9 changed files with 103 additions and 31 deletions

View File

@@ -0,0 +1 @@
-- 1523666882 DOWN add-email-fields-to-reminders

View File

@@ -0,0 +1,4 @@
-- 1523666882 UP add-email-fields-to-reminders
ALTER table reminders add column email varchar(255);
ALTER table reminders add column subject varchar(255);
ALTER table reminders add column body text;

View File

@@ -1,7 +1,9 @@
(ns auto-ap.db.reminders (ns auto-ap.db.reminders
(:require [auto-ap.db.utils :refer [clj->db db->clj get-conn]] (:require [auto-ap.db.utils :refer [clj->db db->clj get-conn]]
[clj-time.core :as time] [clj-time.core :as time]
[amazonica.aws.simpleemail :as ses]
[clojure.java.jdbc :as j] [clojure.java.jdbc :as j]
[config.core :refer [env]]
[clj-time.jdbc :as jdbc] [clj-time.jdbc :as jdbc]
[clojure.string :as str])) [clojure.string :as str]))
@@ -34,9 +36,27 @@
(defn get-ready [] (defn get-ready []
(map db->clj (j/query (get-conn) (map db->clj (j/query (get-conn)
(str (str
" SELECT reminders.*, vendors.name as vendor_name, vendors.email " " SELECT reminders.*, vendors.name as vendor_name "
" FROM reminders INNER join vendors on reminders.vendor_id = vendors.id " " FROM reminders INNER join vendors on reminders.vendor_id = vendors.id "
" WHERE reminders.sent IS NULL AND reminders.scheduled < NOW()")))) " WHERE reminders.sent IS NULL AND reminders.scheduled < NOW()"))))
(defn finish [id] (defn finish [id]
(j/update! (get-conn) :reminders {:sent (time/now)} ["id = ?" id] )) (j/update! (get-conn) :reminders {:sent (time/now)} ["id = ?" id] ))
(defn template []
{:subject "Reminder to send invoices"
:body (str "This is a reminder to please reply with a [format of invoice: pdf/excel]"
"version of last week's invoices for:"
"We would greatly appreciate it if you could send that to us by end of day on"
"Monday.")})
(defn send-emails [reminders]
(doseq [{:keys [vendor-name id email subject body]} reminders]
(when email
(println "Sending email to" email)
(ses/send-email :destination {:to-addresses [email]}
:source (:invoice-email env)
:message {:subject subject
:body {:html (str "<h1>Hello " vendor-name ",</h1><p>" body "</p> <p> - Integreat. </p>")
:text (str "Hello " vendor-name ",\r\n" body "\r\n - Integreat.")}}))
(finish id)))

View File

@@ -1,5 +1,5 @@
(ns auto-ap.routes.reminders (ns auto-ap.routes.reminders
(:require [amazonica.aws.simpleemail :as ses] (:require
[auto-ap.db.reminders :as reminders] [auto-ap.db.reminders :as reminders]
[auto-ap.db.vendors :as vendors] [auto-ap.db.vendors :as vendors]
[auto-ap.routes.utils :refer [wrap-secure]] [auto-ap.routes.utils :refer [wrap-secure]]
@@ -30,24 +30,18 @@
(println "Reminders will happen at" (next-sunday)) (println "Reminders will happen at" (next-sunday))
(println "Reminders to schedule" vendors-without-scheduled) (println "Reminders to schedule" vendors-without-scheduled)
(doseq [{:keys [id invoice-reminder-schedule]} vendors-without-scheduled] (doseq [{:keys [id primary-email invoice-reminder-schedule]} vendors-without-scheduled]
(reminders/insert {:vendor-id id (reminders/insert (assoc (reminders/template)
:scheduled (next-sunday)})))) :vendor-id id
:email primary-email
:scheduled (next-sunday))))))
(defn find-ready-reminders [] (defn find-ready-reminders []
(let [vendors (vendors/get-all) (let [vendors (vendors/get-all)
ready-reminders (reminders/get-ready)] ready-reminders (reminders/get-ready)]
ready-reminders)) ready-reminders))
(defn send-emails [reminders]
(doseq [{:keys [vendor-name email id]} reminders]
(println "Sending email to" email)
(ses/send-email :destination {:to-addresses [email]}
:source (:invoice-email env)
:message {:subject "Reminder to send invoices"
:body {:html (str "<h1>Hello " vendor-name ",</h1><p>This is a reminder to send this week's invoices to us. You can just reply to this email.</p> <p> - Integreat. </p>")
:text (str "Hello " vendor-name ",\r\nThis is a reminder to send this week's invoices to us. You can just reply to this email.\r\n - Integreat.")}})
(reminders/finish id)))
(defn replace-joda [x] (defn replace-joda [x]
(into {} (map (fn [[k v]] (into {} (map (fn [[k v]]
@@ -71,7 +65,7 @@
(println "Scheduling") (println "Scheduling")
(schedule-reminders) (schedule-reminders)
(-> (reminders/get-ready) (-> (reminders/get-ready)
(send-emails))))) (reminders/send-emails)))))
{:status 200 {:status 200
:body "{}" :body "{}"

View File

@@ -2,8 +2,9 @@
(:require [auto-ap.db.vendors :as vendors] (:require [auto-ap.db.vendors :as vendors]
[auto-ap.entities.vendors :as entity] [auto-ap.entities.vendors :as entity]
[auto-ap.routes.utils :refer [wrap-secure wrap-spec]] [auto-ap.routes.utils :refer [wrap-secure wrap-spec]]
[compojure.core :refer [GET POST PUT context defroutes [auto-ap.db.reminders :as reminders]
wrap-routes]])) [clj-time.core :as time]
[compojure.core :refer [GET POST PUT context defroutes wrap-routes]]))
(defroutes routes (defroutes routes
(wrap-routes (wrap-routes
@@ -12,17 +13,32 @@
{:status 200 {:status 200
:body (pr-str (vendors/get-all)) :body (pr-str (vendors/get-all))
:headers {"Content-Type" "application/edn"}}) :headers {"Content-Type" "application/edn"}})
(wrap-spec (wrap-routes
(PUT "/:id" {:keys [edn-params] {:keys [id]} :route-params :as r} (PUT "/:id" {:keys [edn-params] {:keys [id]} :route-params :as r}
{:status 200 {:status 200
:body (pr-str (vendors/upsert id edn-params)) :body (pr-str (vendors/upsert id edn-params))
:headers {"Content-Type" "application/edn"}}) :headers {"Content-Type" "application/edn"}})
::entity/vendor) #(wrap-spec % ::entity/vendor))
(wrap-spec (POST "/:id/remind" {:keys [edn-params] {:keys [id :<< as-int]} :route-params :as r}
(let [id (if (int? id)
id
(Integer/parseInt id))
vendor (vendors/get-by-id id)]
(reminders/insert (assoc
(reminders/template)
:email (:primary-email vendor)
:vendor-id id
:scheduled (time/now)))
(-> (reminders/get-ready)
(reminders/send-emails))
{:status 200
:body "{}"
:headers {"Content-Type" "application/edn"}}))
(wrap-routes
(POST "/" {:keys [edn-params] :as r} (POST "/" {:keys [edn-params] :as r}
{:status 200 {:status 200
:body (pr-str (vendors/insert edn-params)) :body (pr-str (vendors/insert edn-params))
:headers {"Content-Type" "application/edn"}}) :headers {"Content-Type" "application/edn"}})
::entity/vendor)) #(wrap-spec % ::entity/vendor)))
wrap-secure)) wrap-secure))

View File

@@ -42,6 +42,29 @@
:on-success [::save-complete] :on-success [::save-complete]
:on-error [::save-error]})))))) :on-error [::save-error]}))))))
(re-frame/reg-event-fx
::remind
(fn [{:keys [db] :as fx} [_ id]]
{ :http {:method :post
:token (:user db)
:headers {"Content-Type" "application/edn"}
:uri (str "/api/vendors/" id "/remind")
:on-success [::reminder-sent]
:on-error [::failed "Failed to send reminder"]}
:db (assoc-in db [:admin :banner] nil)}))
(re-frame/reg-event-db
::reminder-sent
(fn [db [_ error]]
(-> db
(assoc-in [:admin :banner] "Reminder sent!"))))
(re-frame/reg-event-db
::failed
(fn [db [_ error]]
(-> db
(assoc-in [:admin :banner] error))))
(re-frame/reg-event-db (re-frame/reg-event-db
::save-complete ::save-complete
(fn [db [_ vendor]] (fn [db [_ vendor]]

View File

@@ -18,14 +18,16 @@
[:tr [:tr
[:th "Vendor"] [:th "Vendor"]
[:th "Scheduled Date"] [:th "Scheduled Date"]
[:th "Status"]]] [:th "Status"]
[:tbody (for [{:keys [id vendor-name scheduled sent]} reminders] [:th "Email"]]]
[:tbody (for [{:keys [id vendor-name scheduled sent email ]} reminders]
^{:key id} ^{:key id}
[:tr [:tr
[:td vendor-name] [:td vendor-name]
[:td (date->str scheduled)] [:td (date->str scheduled)]
[:td (when sent [:td (when sent
[:span [:span.icon [:i.fa.fa-check]] "Sent " (date-time->str sent)]) ]])]])) [:span [:span.icon [:i.fa.fa-check]] "Sent " (date-time->str sent)]) ]
[:td email]])]]))
(defn admin-reminders-page [] (defn admin-reminders-page []

View File

@@ -24,14 +24,22 @@
[:tr [:tr
[:th "Name"] [:th "Name"]
[:th "Email"] [:th "Email"]
[:th "Invoice Reminders"]]] [:th "Invoice Reminders"]
[:th]]]
[:tbody (for [v @vendors] [:tbody (for [v @vendors]
^{:key (str (:id v))} ^{:key (str (:id v))}
[:tr {:on-click (fn [] (re-frame/dispatch [::events/edit (:id v)])) [:tr {:on-click (dispatch-event [::events/edit (:id v)])
:style {"cursor" "pointer"}} :style {"cursor" "pointer"}}
[:td (:name v)] [:td (:name v)]
[:td (:primary-email v)] [:td (:primary-email v)]
[:td (:invoice-reminder-schedule v)]])]])) [:td (:invoice-reminder-schedule v)]
[:td
(when (:primary-email v)
[:button.button.is-primary.is-outlined
{:on-click (dispatch-event [::events/remind (:id v)])
:href "#"}
[:span.icon [:i.fa.fa-share-square]] [:span "Send Reminder"]])]])]]))
(defn danger-for [[dom {:keys [field subscription class] :as keys} & rest]] (defn danger-for [[dom {:keys [field subscription class] :as keys} & rest]]
(let [keys (assoc keys :class (str class (let [keys (assoc keys :class (str class
@@ -226,7 +234,7 @@
(when (:saving? editing-vendor) [:div.is-overlay {:style {"backgroundColor" "rgba(150,150,150, 0.5)"}}])] (when (:saving? editing-vendor) [:div.is-overlay {:style {"backgroundColor" "rgba(150,150,150, 0.5)"}}])]
[:footer.modal-card-foot [:footer.modal-card-foot
[:a.button.is-primary {:on-click (fn [] (re-frame/dispatch [::events/save])) [:button.button.is-primary {:on-click (fn [] (re-frame/dispatch [::events/save]))
:disabled (when (not (s/valid? ::entity/vendor editing-vendor )) :disabled (when (not (s/valid? ::entity/vendor editing-vendor ))
"disabled")} "disabled")}
[:span "Save"] [:span "Save"]
@@ -238,17 +246,20 @@
[(with-meta [(with-meta
(fn [] (fn []
[:div.inbox-messages [:div.inbox-messages
(when-let [banner (:banner @(re-frame/subscribe [::subs/admin]))]
[:div.notification banner])
(let [vendors (re-frame/subscribe [::subs/vendors]) (let [vendors (re-frame/subscribe [::subs/vendors])
editing-vendor (:vendor @(re-frame/subscribe [::subs/admin]))] editing-vendor (:vendor @(re-frame/subscribe [::subs/admin]))]
[:div [:div
[:h1.title "Vendors"] [:h1.title "Vendors"]
[vendors-table] [vendors-table]
[:a.button.is-primary.is-large {:on-click (dispatch-event [::events/new])} "New vendor"] [:div.is-pulled-right
[:a.button.is-primary.is-large {:on-click (dispatch-event [::events/new])} "New vendor"]]
(when editing-vendor (when editing-vendor
[edit-dialog] [edit-dialog])])])
)])])
{:component-did-mount (fn [] {:component-did-mount (fn []
(re-frame/dispatch [::events/mounted]))})]) (re-frame/dispatch [::events/mounted]))})])

View File

@@ -18,6 +18,7 @@
(defn dispatch-event [event] (defn dispatch-event [event]
(fn [e] (fn [e]
(.stopPropagation e)
(.preventDefault e) (.preventDefault e)
(re-frame/dispatch event))) (re-frame/dispatch event)))