mirror of
https://github.com/penpot/penpot.git
synced 2026-10-28 21:40:34 +00:00
🎉 Add SREPL helpers to update emails and clear bounce reports (#12668)
Support currently fixes wrong profile emails and false bounce marks with raw SQL. Add update-profile-email! (rejects collisions) and clear-email-complaint-reports! (deletes global and profile reports and unmutes the matching profile) so these ops run through the REPL. Closes #12663
This commit is contained in:
parent
f1d15aee4b
commit
00dec9143d
@ -169,6 +169,89 @@
|
||||
(db/get-update-count)
|
||||
(pos?)))))))
|
||||
|
||||
(defn update-profile-email!
|
||||
"Support helper: change a profile's email address.
|
||||
|
||||
Looks up the profile by its current email. Rejects with
|
||||
`:email-already-exists` when the new address is already used by
|
||||
another non-deleted profile. Does not clear bounce/complaint
|
||||
reports; call `clear-email-complaint-reports!` for that."
|
||||
[& {:keys [email new-email]}]
|
||||
(assert (string? email) "expected email")
|
||||
(assert (string? new-email) "expected new-email")
|
||||
|
||||
(let [email (profile/clean-email email)
|
||||
new-email (profile/clean-email new-email)]
|
||||
(db/tx-run! sys/system
|
||||
(fn [{:keys [::db/conn] :as system}]
|
||||
(let [profile (profile/get-profile-by-email conn email)]
|
||||
(when-not profile
|
||||
(ex/raise :type :not-found
|
||||
:code :profile-not-found
|
||||
:hint "profile not found for email"
|
||||
:email email))
|
||||
|
||||
(if (= email new-email)
|
||||
:unchanged
|
||||
(do
|
||||
(when (profile/get-profile-by-email conn new-email)
|
||||
(ex/raise :type :validation
|
||||
:code :email-already-exists))
|
||||
|
||||
(db/update! conn :profile
|
||||
{:email new-email}
|
||||
{:id (:id profile)}
|
||||
{::db/return-keys false})
|
||||
|
||||
(audit/insert system
|
||||
{:name "update-profile-email"
|
||||
:type "action"
|
||||
:props {:id (:id profile)
|
||||
:email email
|
||||
:new-email new-email}
|
||||
:context {:triggered-by "srepl"
|
||||
:cause "explicit call to update-profile-email!"}})
|
||||
:updated)))))))
|
||||
|
||||
(defn clear-email-complaint-reports!
|
||||
"Support helper: delete bounce and complaint reports for an email
|
||||
from `global_complaint_report` and, when a profile owns that email,
|
||||
from `profile_complaint_report`. Also unmutes that profile so mail
|
||||
can be sent again after a false-positive bounce or complaint."
|
||||
[email]
|
||||
(assert (string? email) "expected email")
|
||||
|
||||
(let [email (profile/clean-email email)]
|
||||
(db/tx-run! sys/system
|
||||
(fn [{:keys [::db/conn] :as system}]
|
||||
(let [profile (db/get* conn :profile {:email email})
|
||||
global-deleted
|
||||
(-> (db/delete! conn :global-complaint-report {:email email})
|
||||
(db/get-update-count))
|
||||
profile-deleted
|
||||
(if profile
|
||||
(-> (db/delete! conn :profile-complaint-report
|
||||
{:profile-id (:id profile)})
|
||||
(db/get-update-count))
|
||||
0)]
|
||||
(when profile
|
||||
(db/update! conn :profile
|
||||
{:is-muted false}
|
||||
{:id (:id profile)}
|
||||
{::db/return-keys false}))
|
||||
|
||||
(when (or profile (pos? global-deleted))
|
||||
(audit/insert system
|
||||
{:name "clear-email-complaint-reports"
|
||||
:type "action"
|
||||
:props (cond-> {:email email
|
||||
:global-deleted global-deleted
|
||||
:profile-deleted profile-deleted}
|
||||
profile (assoc :id (:id profile)))
|
||||
:context {:triggered-by "srepl"
|
||||
:cause "explicit call to clear-email-complaint-reports!"}})
|
||||
:cleared))))))
|
||||
|
||||
(defn parse-emails
|
||||
"Parse the emails into a seq of cleaned emails. Accepts a single
|
||||
email, a comma separated list of emails or a coll of emails.
|
||||
|
||||
@ -205,3 +205,104 @@
|
||||
;; to the zero one.
|
||||
(t/is (nil? (:profile-id event)))
|
||||
(t/is (= (:id profile) (get-in event [:props :id])))))))))
|
||||
|
||||
(t/deftest update-profile-email-changes-email
|
||||
(with-mocks [audit-mock {:target 'app.loggers.audit/insert :return nil}]
|
||||
(with-redefs [sys/system th/*system*]
|
||||
(let [profile (th/create-profile* 1 {:is-active true})
|
||||
result (srepl/update-profile-email!
|
||||
:email (:email profile)
|
||||
:new-email "corrected@example.com")]
|
||||
(t/is (= :updated result))
|
||||
(t/is (= "corrected@example.com"
|
||||
(:email (th/db-get :profile {:id (:id profile)}))))
|
||||
(let [event (->> (:call-args-list @audit-mock)
|
||||
(map second)
|
||||
first)]
|
||||
(t/is (= "update-profile-email" (:name event)))
|
||||
(t/is (= "action" (:type event)))
|
||||
(t/is (= (:id profile) (get-in event [:props :id])))
|
||||
(t/is (= (:email profile) (get-in event [:props :email])))
|
||||
(t/is (= "corrected@example.com"
|
||||
(get-in event [:props :new-email]))))))))
|
||||
|
||||
(t/deftest update-profile-email-rejects-collision
|
||||
(with-redefs [sys/system th/*system*]
|
||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||
profile2 (th/create-profile* 2 {:is-active true})
|
||||
error (try
|
||||
(srepl/update-profile-email!
|
||||
:email (:email profile1)
|
||||
:new-email (:email profile2))
|
||||
(catch Throwable e e))]
|
||||
(t/is (th/ex-of-code? error :email-already-exists))
|
||||
(t/is (= (:email profile1)
|
||||
(:email (th/db-get :profile {:id (:id profile1)})))))))
|
||||
|
||||
(t/deftest update-profile-email-unchanged-when-same
|
||||
(with-mocks [audit-mock {:target 'app.loggers.audit/insert :return nil}]
|
||||
(with-redefs [sys/system th/*system*]
|
||||
(let [profile (th/create-profile* 1 {:is-active true})
|
||||
result (srepl/update-profile-email!
|
||||
:email (:email profile)
|
||||
:new-email (:email profile))]
|
||||
(t/is (= :unchanged result))
|
||||
(t/is (empty? (:call-args-list @audit-mock)))))))
|
||||
|
||||
(t/deftest update-profile-email-not-found
|
||||
(with-mocks [audit-mock {:target 'app.loggers.audit/insert :return nil}]
|
||||
(with-redefs [sys/system th/*system*]
|
||||
(let [error (try
|
||||
(srepl/update-profile-email!
|
||||
:email "missing@example.com"
|
||||
:new-email "corrected@example.com")
|
||||
(catch Throwable e e))]
|
||||
(t/is (th/ex-of-code? error :profile-not-found))
|
||||
(t/is (= :not-found (:type (ex-data error))))
|
||||
(t/is (empty? (:call-args-list @audit-mock)))))))
|
||||
|
||||
(t/deftest clear-email-complaint-reports-deletes-and-unmutes
|
||||
(with-mocks [audit-mock {:target 'app.loggers.audit/insert :return nil}]
|
||||
(with-redefs [sys/system th/*system*]
|
||||
(let [profile (th/create-profile* 1 {:is-active true :is-muted true
|
||||
:email "bounced@example.com"})
|
||||
_ (th/create-complaint-for th/*system*
|
||||
{:id (:id profile) :type :bounce})
|
||||
_ (th/create-complaint-for th/*system*
|
||||
{:id (:id profile) :type :complaint})
|
||||
_ (th/create-global-complaint-for th/*system*
|
||||
{:email (:email profile)
|
||||
:type :bounce})
|
||||
_ (th/create-global-complaint-for th/*system*
|
||||
{:email (:email profile)
|
||||
:type :complaint})
|
||||
result (srepl/clear-email-complaint-reports! (:email profile))]
|
||||
(t/is (= :cleared result))
|
||||
(t/is (empty? (th/db-query :global-complaint-report
|
||||
{:email (:email profile)})))
|
||||
(t/is (empty? (th/db-query :profile-complaint-report
|
||||
{:profile-id (:id profile)})))
|
||||
(t/is (false? (:is-muted (th/db-get :profile {:id (:id profile)}))))
|
||||
(let [event (->> (:call-args-list @audit-mock)
|
||||
(map second)
|
||||
first)]
|
||||
(t/is (= "clear-email-complaint-reports" (:name event)))
|
||||
(t/is (= "action" (:type event)))
|
||||
(t/is (= (:id profile) (get-in event [:props :id])))
|
||||
(t/is (= (:email profile) (get-in event [:props :email]))))))))
|
||||
|
||||
(t/deftest clear-email-complaint-reports-without-profile
|
||||
(with-mocks [audit-mock {:target 'app.loggers.audit/insert :return nil}]
|
||||
(with-redefs [sys/system th/*system*]
|
||||
(let [email "orphan-bounce@example.com"
|
||||
_ (th/create-global-complaint-for th/*system*
|
||||
{:email email :type :bounce})
|
||||
result (srepl/clear-email-complaint-reports! email)]
|
||||
(t/is (= :cleared result))
|
||||
(t/is (empty? (th/db-query :global-complaint-report {:email email})))
|
||||
(let [event (->> (:call-args-list @audit-mock)
|
||||
(map second)
|
||||
first)]
|
||||
(t/is (= "clear-email-complaint-reports" (:name event)))
|
||||
(t/is (= email (get-in event [:props :email])))
|
||||
(t/is (nil? (get-in event [:props :id]))))))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user