Improve object deletion process on profile deletion

This commit is contained in:
Andrey Antukh 2022-09-22 17:47:05 +02:00
parent 06bce92cdc
commit 278f6685b6
4 changed files with 57 additions and 62 deletions

View file

@ -252,6 +252,7 @@
;; --- MUTATION: Delete Profile ;; --- MUTATION: Delete Profile
(declare get-owned-teams-with-participants)
(declare check-can-delete-profile!) (declare check-can-delete-profile!)
(declare mark-profile-as-deleted!) (declare mark-profile-as-deleted!)
@ -261,14 +262,29 @@
(sv/defmethod ::delete-profile (sv/defmethod ::delete-profile
[{:keys [pool session] :as cfg} {:keys [profile-id] :as params}] [{:keys [pool session] :as cfg} {:keys [profile-id] :as params}]
(db/with-atomic [conn pool] (db/with-atomic [conn pool]
(check-can-delete-profile! conn profile-id) (let [teams (get-owned-teams-with-participants conn profile-id)
deleted-at (dt/now)]
(db/update! conn :profile ;; If we found owned teams with participants, we don't allow
{:deleted-at (dt/now)} ;; delete profile until the user properly transfer ownership or
{:id profile-id}) ;; explicitly removes all participants from the team
(when (some pos? (map :participants teams))
(ex/raise :type :validation
:code :owner-teams-with-people
:hint "The user need to transfer ownership of owned teams."
:context {:teams (mapv :id teams)}))
(with-meta {} (doseq [{:keys [id]} teams]
{:transform-response (:delete session)}))) (db/update! conn :team
{:deleted-at deleted-at}
{:id id}))
(db/update! conn :profile
{:deleted-at deleted-at}
{:id profile-id})
(with-meta {}
{:transform-response (:delete session)}))))
(def sql:owned-teams (def sql:owned-teams
"with owner_teams as ( "with owner_teams as (
@ -277,23 +293,16 @@
where tpr.is_owner is true where tpr.is_owner is true
and tpr.profile_id = ? and tpr.profile_id = ?
) )
select tpr.team_id, select tpr.team_id as id,
count(tpr.profile_id) as num_profiles count(tpr.profile_id) - 1 as participants
from team_profile_rel as tpr from team_profile_rel as tpr
where tpr.team_id in (select id from owner_teams) where tpr.team_id in (select id from owner_teams)
and tpr.profile_id != ?
group by 1") group by 1")
(defn- check-can-delete-profile! (defn- get-owned-teams-with-participants
[conn profile-id] [conn profile-id]
(let [rows (db/exec! conn [sql:owned-teams profile-id])] (db/exec! conn [sql:owned-teams profile-id profile-id]))
;; If we found owned teams with more than one profile we don't
;; allow delete profile until the user properly transfer ownership
;; or explicitly removes all participants from the team.
(when (some #(> (:num-profiles %) 1) rows)
(ex/raise :type :validation
:code :owner-teams-with-people
:hint "The user need to transfer ownership of owned teams."
:context {:teams (mapv :team-id rows)}))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEPRECATED METHODS (TO BE REMOVED ON 1.16.x) ;; DEPRECATED METHODS (TO BE REMOVED ON 1.16.x)

View file

@ -62,7 +62,7 @@
(cmd.auth/send-email-verification! pool sprops profile) (cmd.auth/send-email-verification! pool sprops profile)
:email-sent)) :email-sent))
(defn update-profile (defn update-profile!
"Update a limited set of profile attrs." "Update a limited set of profile attrs."
[system & {:keys [email id active? deleted? blocked?]}] [system & {:keys [email id active? deleted? blocked?]}]

View file

@ -69,8 +69,7 @@
(defmethod delete-objects "team_font_variant" (defmethod delete-objects "team_font_variant"
[{:keys [conn min-age storage table] :as cfg}] [{:keys [conn min-age storage table] :as cfg}]
(let [sql (str/fmt sql:delete-objects (let [sql (str/fmt sql:delete-objects {:table table :limit 50})
{:table table :limit 50})
fonts (db/exec! conn [sql min-age]) fonts (db/exec! conn [sql min-age])
storage (media/configure-assets-storage storage conn)] storage (media/configure-assets-storage storage conn)]
(doseq [{:keys [id] :as font} fonts] (doseq [{:keys [id] :as font} fonts]
@ -85,10 +84,9 @@
(defmethod delete-objects "team" (defmethod delete-objects "team"
[{:keys [conn min-age storage table] :as cfg}] [{:keys [conn min-age storage table] :as cfg}]
(let [sql (str/fmt sql:delete-objects (let [sql (str/fmt sql:delete-objects {:table table :limit 50})
{:table table :limit 50})
teams (db/exec! conn [sql min-age]) teams (db/exec! conn [sql min-age])
storage (assoc storage :conn conn)] storage (media/configure-assets-storage storage conn)]
(doseq [{:keys [id] :as team} teams] (doseq [{:keys [id] :as team} teams]
(l/debug :hint "permanently delete object" :table table :id id) (l/debug :hint "permanently delete object" :table table :id id)
@ -103,32 +101,17 @@
where deleted_at is not null where deleted_at is not null
and deleted_at < now() - ?::interval and deleted_at < now() - ?::interval
order by deleted_at order by deleted_at
limit %(limit)s limit ?
for update") for update")
(def sql:mark-owned-teams-deleted
"with owned as (
select tpr.team_id as id
from team_profile_rel as tpr
where tpr.is_owner is true
and tpr.profile_id = ?
)
update team set deleted_at = now() - ?::interval
where id in (select id from owned)")
(defmethod delete-objects "profile" (defmethod delete-objects "profile"
[{:keys [conn min-age storage table] :as cfg}] [{:keys [conn min-age storage table] :as cfg}]
(let [sql (str/fmt sql:retrieve-deleted-profiles {:limit 50}) (let [profiles (db/exec! conn [sql:retrieve-deleted-profiles min-age 50])
profiles (db/exec! conn [sql min-age]) storage (media/configure-assets-storage storage conn)]
storage (assoc storage :conn conn)]
(doseq [{:keys [id] :as profile} profiles] (doseq [{:keys [id] :as profile} profiles]
(l/debug :hint "permanently delete object" :table table :id id) (l/debug :hint "permanently delete object" :table table :id id)
;; Mark the owned teams as deleted; this enables them to be processed
;; in the same transaction in the "team" table step.
(db/exec-one! conn [sql:mark-owned-teams-deleted id min-age])
;; Mark as deleted the storage object related with the photo-id ;; Mark as deleted the storage object related with the photo-id
;; field. ;; field.
(some->> (:photo-id profile) (sto/touch-object! storage) deref) (some->> (:photo-id profile) (sto/touch-object! storage) deref)
@ -164,22 +147,23 @@
(defmethod ig/init-key ::handler (defmethod ig/init-key ::handler
[_ {:keys [pool] :as cfg}] [_ {:keys [pool] :as cfg}]
(fn [params] (fn [params]
;; Checking first on task argument allows properly testing it. (db/with-atomic [conn pool]
(let [min-age (or (:min-age params) (:min-age cfg))] (let [min-age (or (:min-age params) (:min-age cfg))
(db/with-atomic [conn pool] cfg (-> cfg
(let [cfg (-> cfg (assoc :min-age (db/interval min-age))
(assoc :min-age (db/interval min-age)) (assoc :conn conn))]
(assoc :conn conn))] (loop [tables (seq target-tables)
(loop [tables (seq target-tables) total 0]
total 0] (if-let [table (first tables)]
(if-let [table (first tables)] (recur (rest tables)
(recur (rest tables) (+ total (process-table (assoc cfg :table table))))
(+ total (process-table (assoc cfg :table table)))) (do
(do (l/info :hint "objects gc finished succesfully"
(l/info :hint "task finished" :min-age (dt/format-duration min-age) :total total) :min-age (dt/format-duration min-age)
:total total)
(when (:rollback? params) (when (:rollback? params)
(db/rollback! conn)) (db/rollback! conn))
{:processed total})))))))) {:processed total})))))))

View file

@ -537,10 +537,12 @@
:file-id (:id file) :file-id (:id file)
:object-id frame1-id :object-id frame1-id
:components-v2 true} :components-v2 true}
{:keys [error result] :as out} (th/query! data)] out (th/query! data)]
;; (th/print-result! out)
(t/is (th/ex-of-type? error :validation)) (t/is (not (th/success? out)))
(t/is (th/ex-of-code? error :spec-validation (th/ex-code error))))) (let [{:keys [type code]} (-> out :error ex-data)]
(t/is (= :validation type))
(t/is (= :spec-validation code)))))
(t/testing "RPC :file-data-for-thumbnail" (t/testing "RPC :file-data-for-thumbnail"
;; Insert a thumbnail data for the frame-id ;; Insert a thumbnail data for the frame-id