mirror of
https://github.com/penpot/penpot.git
synced 2025-01-22 22:49:01 -05:00
🐛 Make profile deletion follow the delete-object flow
This removes the need of the specific task for cleaning orphan teams.
This commit is contained in:
parent
56476acc19
commit
f9af7f0f09
6 changed files with 83 additions and 118 deletions
|
@ -343,7 +343,6 @@
|
||||||
::wrk/tasks
|
::wrk/tasks
|
||||||
{:sendmail (ig/ref ::email/handler)
|
{:sendmail (ig/ref ::email/handler)
|
||||||
:objects-gc (ig/ref :app.tasks.objects-gc/handler)
|
:objects-gc (ig/ref :app.tasks.objects-gc/handler)
|
||||||
:orphan-teams-gc (ig/ref :app.tasks.orphan-teams-gc/handler)
|
|
||||||
:file-gc (ig/ref :app.tasks.file-gc/handler)
|
:file-gc (ig/ref :app.tasks.file-gc/handler)
|
||||||
:file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler)
|
:file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler)
|
||||||
:tasks-gc (ig/ref :app.tasks.tasks-gc/handler)
|
:tasks-gc (ig/ref :app.tasks.tasks-gc/handler)
|
||||||
|
@ -388,9 +387,6 @@
|
||||||
{::db/pool (ig/ref ::db/pool)
|
{::db/pool (ig/ref ::db/pool)
|
||||||
::sto/storage (ig/ref ::sto/storage)}
|
::sto/storage (ig/ref ::sto/storage)}
|
||||||
|
|
||||||
:app.tasks.orphan-teams-gc/handler
|
|
||||||
{::db/pool (ig/ref ::db/pool)}
|
|
||||||
|
|
||||||
:app.tasks.delete-object/handler
|
:app.tasks.delete-object/handler
|
||||||
{::db/pool (ig/ref ::db/pool)}
|
{::db/pool (ig/ref ::db/pool)}
|
||||||
|
|
||||||
|
@ -479,9 +475,6 @@
|
||||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||||
:task :objects-gc}
|
:task :objects-gc}
|
||||||
|
|
||||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
|
||||||
:task :orphan-teams-gc}
|
|
||||||
|
|
||||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||||
:task :storage-gc-deleted}
|
:task :storage-gc-deleted}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[app.worker :as-alias wrk]
|
[app.worker :as wrk]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[promesa.exec :as px]))
|
[promesa.exec :as px]))
|
||||||
|
|
||||||
|
@ -366,13 +366,13 @@
|
||||||
|
|
||||||
;; --- MUTATION: Delete Profile
|
;; --- MUTATION: Delete Profile
|
||||||
|
|
||||||
(declare ^:private get-owned-teams-with-participants)
|
(declare ^:private get-owned-teams)
|
||||||
|
|
||||||
(sv/defmethod ::delete-profile
|
(sv/defmethod ::delete-profile
|
||||||
{::doc/added "1.0"}
|
{::doc/added "1.0"}
|
||||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [teams (get-owned-teams-with-participants conn profile-id)
|
(let [teams (get-owned-teams conn profile-id)
|
||||||
deleted-at (dt/now)]
|
deleted-at (dt/now)]
|
||||||
|
|
||||||
;; If we found owned teams with participants, we don't allow
|
;; If we found owned teams with participants, we don't allow
|
||||||
|
@ -384,15 +384,18 @@
|
||||||
:hint "The user need to transfer ownership of owned teams."
|
:hint "The user need to transfer ownership of owned teams."
|
||||||
:context {:teams (mapv :id teams)}))
|
:context {:teams (mapv :id teams)}))
|
||||||
|
|
||||||
(doseq [{:keys [id]} teams]
|
;; Mark profile deleted immediatelly
|
||||||
(db/update! conn :team
|
|
||||||
{:deleted-at deleted-at}
|
|
||||||
{:id id}))
|
|
||||||
|
|
||||||
(db/update! conn :profile
|
(db/update! conn :profile
|
||||||
{:deleted-at deleted-at}
|
{:deleted-at deleted-at}
|
||||||
{:id profile-id})
|
{:id profile-id})
|
||||||
|
|
||||||
|
;; Schedule cascade deletion to a worker
|
||||||
|
(wrk/submit! {::db/conn conn
|
||||||
|
::wrk/task :delete-object
|
||||||
|
::wrk/params {:object :profile
|
||||||
|
:deleted-at deleted-at
|
||||||
|
:id profile-id}})
|
||||||
|
|
||||||
(rph/with-transform {} (session/delete-fn cfg)))))
|
(rph/with-transform {} (session/delete-fn cfg)))))
|
||||||
|
|
||||||
|
|
||||||
|
@ -406,15 +409,14 @@
|
||||||
AND tpr.profile_id = ?
|
AND tpr.profile_id = ?
|
||||||
)
|
)
|
||||||
SELECT tpr.team_id AS id,
|
SELECT tpr.team_id AS id,
|
||||||
count(tpr.profile_id) AS participants
|
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- get-owned-teams-with-participants
|
(defn get-owned-teams
|
||||||
[conn profile-id]
|
[conn profile-id]
|
||||||
(db/exec! conn [sql:owned-teams profile-id profile-id]))
|
(db/exec! conn [sql:owned-teams profile-id]))
|
||||||
|
|
||||||
(def ^:private sql:profile-existence
|
(def ^:private sql:profile-existence
|
||||||
"select exists (select * from profile
|
"select exists (select * from profile
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.rpc.commands.profile :as profile]
|
||||||
|
[app.util.time :as dt]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
|
@ -21,7 +23,9 @@
|
||||||
(defmethod delete-object :file
|
(defmethod delete-object :file
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
|
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
|
||||||
(when-let [file (db/get* conn :file {:id id} {::db/remove-deleted false})]
|
(when-let [file (db/get* conn :file {:id id} {::db/remove-deleted false})]
|
||||||
(l/trc :hint "marking for deletion" :rel "file" :id (str id))
|
(l/trc :hint "marking for deletion" :rel "file" :id (str id)
|
||||||
|
:deleted-at (dt/format-instant deleted-at))
|
||||||
|
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:deleted-at deleted-at}
|
{:deleted-at deleted-at}
|
||||||
{:id id}
|
{:id id}
|
||||||
|
@ -53,7 +57,9 @@
|
||||||
|
|
||||||
(defmethod delete-object :project
|
(defmethod delete-object :project
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
|
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
|
||||||
(l/trc :hint "marking for deletion" :rel "project" :id (str id))
|
(l/trc :hint "marking for deletion" :rel "project" :id (str id)
|
||||||
|
:deleted-at (dt/format-instant deleted-at))
|
||||||
|
|
||||||
(db/update! conn :project
|
(db/update! conn :project
|
||||||
{:deleted-at deleted-at}
|
{:deleted-at deleted-at}
|
||||||
{:id id}
|
{:id id}
|
||||||
|
@ -68,7 +74,8 @@
|
||||||
|
|
||||||
(defmethod delete-object :team
|
(defmethod delete-object :team
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
|
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
|
||||||
(l/trc :hint "marking for deletion" :rel "team" :id (str id))
|
(l/trc :hint "marking for deletion" :rel "team" :id (str id)
|
||||||
|
:deleted-at (dt/format-instant deleted-at))
|
||||||
(db/update! conn :team
|
(db/update! conn :team
|
||||||
{:deleted-at deleted-at}
|
{:deleted-at deleted-at}
|
||||||
{:id id}
|
{:id id}
|
||||||
|
@ -87,6 +94,20 @@
|
||||||
:object :project
|
:object :project
|
||||||
:deleted-at deleted-at)))))
|
:deleted-at deleted-at)))))
|
||||||
|
|
||||||
|
(defmethod delete-object :profile
|
||||||
|
[{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}]
|
||||||
|
(l/trc :hint "marking for deletion" :rel "profile" :id (str id)
|
||||||
|
:deleted-at (dt/format-instant deleted-at))
|
||||||
|
|
||||||
|
(db/update! conn :profile
|
||||||
|
{:deleted-at deleted-at}
|
||||||
|
{:id id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
|
||||||
|
(doseq [team (profile/get-owned-teams conn id)]
|
||||||
|
(delete-object cfg (assoc team
|
||||||
|
:object :team
|
||||||
|
:deleted-at deleted-at))))
|
||||||
|
|
||||||
(defmethod delete-object :default
|
(defmethod delete-object :default
|
||||||
[_cfg props]
|
[_cfg props]
|
||||||
|
|
|
@ -35,11 +35,6 @@
|
||||||
;; Mark as deleted the storage object
|
;; Mark as deleted the storage object
|
||||||
(some->> photo-id (sto/touch-object! storage))
|
(some->> photo-id (sto/touch-object! storage))
|
||||||
|
|
||||||
;; And finally, permanently delete the profile. The
|
|
||||||
;; relevant objects will be deleted using DELETE
|
|
||||||
;; CASCADE database triggers. This may leave orphan
|
|
||||||
;; teams, but there is a special task for deleting
|
|
||||||
;; orphaned teams.
|
|
||||||
(db/delete! conn :profile {:id id})
|
(db/delete! conn :profile {:id id})
|
||||||
|
|
||||||
(inc total))
|
(inc total))
|
||||||
|
@ -269,15 +264,15 @@
|
||||||
0)))
|
0)))
|
||||||
|
|
||||||
(def ^:private deletion-proc-vars
|
(def ^:private deletion-proc-vars
|
||||||
[#'delete-file-media-objects!
|
[#'delete-profiles!
|
||||||
|
#'delete-file-media-objects!
|
||||||
#'delete-file-data-fragments!
|
#'delete-file-data-fragments!
|
||||||
#'delete-file-object-thumbnails!
|
#'delete-file-object-thumbnails!
|
||||||
#'delete-file-thumbnails!
|
#'delete-file-thumbnails!
|
||||||
#'delete-files!
|
#'delete-files!
|
||||||
#'delete-projects!
|
#'delete-projects!
|
||||||
#'delete-fonts!
|
#'delete-fonts!
|
||||||
#'delete-teams!
|
#'delete-teams!])
|
||||||
#'delete-profiles!])
|
|
||||||
|
|
||||||
(defn- execute-proc!
|
(defn- execute-proc!
|
||||||
"A generic function that executes the specified proc iterativelly
|
"A generic function that executes the specified proc iterativelly
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) KALEIDOS INC
|
|
||||||
|
|
||||||
(ns app.tasks.orphan-teams-gc
|
|
||||||
"A maintenance task that performs orphan teams GC."
|
|
||||||
(:require
|
|
||||||
[app.common.logging :as l]
|
|
||||||
[app.db :as db]
|
|
||||||
[app.util.time :as dt]
|
|
||||||
[app.worker :as wrk]
|
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[integrant.core :as ig]))
|
|
||||||
|
|
||||||
(def ^:private sql:get-orphan-teams
|
|
||||||
"SELECT t.id
|
|
||||||
FROM team AS t
|
|
||||||
LEFT JOIN team_profile_rel AS tpr
|
|
||||||
ON (t.id = tpr.team_id)
|
|
||||||
WHERE tpr.profile_id IS NULL
|
|
||||||
AND t.deleted_at IS NULL
|
|
||||||
ORDER BY t.created_at ASC
|
|
||||||
FOR UPDATE OF t
|
|
||||||
SKIP LOCKED")
|
|
||||||
|
|
||||||
(defn- delete-orphan-teams
|
|
||||||
"Find all orphan teams (with no members) and mark them for
|
|
||||||
deletion (soft delete)."
|
|
||||||
[{:keys [::db/conn] :as cfg}]
|
|
||||||
(let [deleted-at (dt/now)]
|
|
||||||
(->> (db/cursor conn sql:get-orphan-teams)
|
|
||||||
(map :id)
|
|
||||||
(reduce (fn [total team-id]
|
|
||||||
(l/trc :hint "mark orphan team for deletion" :id (str team-id))
|
|
||||||
|
|
||||||
(db/update! conn :team
|
|
||||||
{:deleted-at deleted-at}
|
|
||||||
{:id team-id})
|
|
||||||
|
|
||||||
(wrk/submit! (-> cfg
|
|
||||||
(assoc ::wrk/task :delete-object)
|
|
||||||
(assoc ::wrk/params {:object :team
|
|
||||||
:deleted-at deleted-at
|
|
||||||
:id team-id})))
|
|
||||||
(inc total))
|
|
||||||
0))))
|
|
||||||
|
|
||||||
(defmethod ig/pre-init-spec ::handler [_]
|
|
||||||
(s/keys :req [::db/pool]))
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::handler
|
|
||||||
[_ cfg]
|
|
||||||
(fn [{:keys [props] :as task}]
|
|
||||||
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
|
||||||
(let [total (delete-orphan-teams cfg)]
|
|
||||||
(l/inf :hint "task finished"
|
|
||||||
:teams total
|
|
||||||
:rollback? (boolean (:rollback? props)))
|
|
||||||
|
|
||||||
(when (:rollback? props)
|
|
||||||
(db/rollback! conn))
|
|
||||||
|
|
||||||
{:processed total})))))
|
|
|
@ -153,23 +153,22 @@
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (= 1 (count (:result out)))))
|
(t/is (= 1 (count (:result out)))))
|
||||||
|
|
||||||
;; execute permanent deletion task
|
(th/run-pending-tasks!)
|
||||||
(let [result (th/run-task! :objects-gc {:min-age 0})]
|
|
||||||
(t/is (= 1 (:processed result))))
|
|
||||||
|
|
||||||
(let [row (th/db-get :team
|
|
||||||
{:id (:default-team-id prof)}
|
|
||||||
{::db/remove-deleted false})]
|
|
||||||
(t/is (nil? (:deleted-at row))))
|
|
||||||
|
|
||||||
(let [result (th/run-task! :orphan-teams-gc {:min-age 0})]
|
|
||||||
(t/is (= 1 (:processed result))))
|
|
||||||
|
|
||||||
(let [row (th/db-get :team
|
(let [row (th/db-get :team
|
||||||
{:id (:default-team-id prof)}
|
{:id (:default-team-id prof)}
|
||||||
{::db/remove-deleted false})]
|
{::db/remove-deleted false})]
|
||||||
(t/is (dt/instant? (:deleted-at row))))
|
(t/is (dt/instant? (:deleted-at row))))
|
||||||
|
|
||||||
|
;; execute permanent deletion task
|
||||||
|
(let [result (th/run-task! :objects-gc {:min-age 0})]
|
||||||
|
(t/is (= 4 (:processed result))))
|
||||||
|
|
||||||
|
(let [row (th/db-get :team
|
||||||
|
{:id (:default-team-id prof)}
|
||||||
|
{::db/remove-deleted false})]
|
||||||
|
(t/is (nil? row)))
|
||||||
|
|
||||||
;; query profile after delete
|
;; query profile after delete
|
||||||
(let [params {::th/type :get-profile
|
(let [params {::th/type :get-profile
|
||||||
::rpc/profile-id (:id prof)}
|
::rpc/profile-id (:id prof)}
|
||||||
|
@ -259,7 +258,6 @@
|
||||||
(t/is (= (:type edata) :validation))
|
(t/is (= (:type edata) :validation))
|
||||||
(t/is (= (:code edata) :owner-teams-with-people))))
|
(t/is (= (:code edata) :owner-teams-with-people))))
|
||||||
|
|
||||||
|
|
||||||
;; Leave team by role 0 (the default) and reassing owner to role 3
|
;; Leave team by role 0 (the default) and reassing owner to role 3
|
||||||
;; without reassinging it (should fail)
|
;; without reassinging it (should fail)
|
||||||
(let [params {::th/type :leave-team
|
(let [params {::th/type :leave-team
|
||||||
|
@ -287,7 +285,7 @@
|
||||||
(t/is (nil? (:result out)))
|
(t/is (nil? (:result out)))
|
||||||
(t/is (nil? (:error out))))
|
(t/is (nil? (:error out))))
|
||||||
|
|
||||||
;; Request profile to be deleted (it should fail)
|
;; Request profile to be deleted
|
||||||
(let [params {::th/type :delete-profile
|
(let [params {::th/type :delete-profile
|
||||||
::rpc/profile-id (:id prof1)}
|
::rpc/profile-id (:id prof1)}
|
||||||
out (th/command! params)]
|
out (th/command! params)]
|
||||||
|
@ -305,22 +303,16 @@
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (= 1 (count (:result out)))))
|
(t/is (= 1 (count (:result out)))))
|
||||||
|
|
||||||
|
(th/run-pending-tasks!)
|
||||||
|
|
||||||
;; execute permanent deletion task
|
;; execute permanent deletion task
|
||||||
(let [result (th/run-task! :objects-gc {:min-age 0})]
|
(let [result (th/run-task! :objects-gc {:min-age 0})]
|
||||||
(t/is (= 1 (:processed result))))
|
(t/is (= 4 (:processed result))))
|
||||||
|
|
||||||
(let [row (th/db-get :team
|
(let [row (th/db-get :team
|
||||||
{:id (:default-team-id prof1)}
|
{:id (:default-team-id prof1)}
|
||||||
{::db/remove-deleted false})]
|
{::db/remove-deleted false})]
|
||||||
(t/is (nil? (:deleted-at row))))
|
(t/is (nil? row)))
|
||||||
|
|
||||||
(let [result (th/run-task! :orphan-teams-gc {:min-age 0})]
|
|
||||||
(t/is (= 1 (:processed result))))
|
|
||||||
|
|
||||||
(let [row (th/db-get :team
|
|
||||||
{:id (:default-team-id prof1)}
|
|
||||||
{::db/remove-deleted false})]
|
|
||||||
(t/is (dt/instant? (:deleted-at row))))
|
|
||||||
|
|
||||||
;; query profile after delete
|
;; query profile after delete
|
||||||
(let [params {::th/type :get-profile
|
(let [params {::th/type :get-profile
|
||||||
|
@ -330,6 +322,33 @@
|
||||||
(let [result (:result out)]
|
(let [result (:result out)]
|
||||||
(t/is (= uuid/zero (:id result)))))))
|
(t/is (= uuid/zero (:id result)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest profile-deletion-4
|
||||||
|
(let [prof1 (th/create-profile* 1)
|
||||||
|
file1 (th/create-file* 1 {:profile-id (:id prof1)
|
||||||
|
:project-id (:default-project-id prof1)
|
||||||
|
:is-shared false})
|
||||||
|
team1 (th/create-team* 1 {:profile-id (:id prof1)})
|
||||||
|
team2 (th/create-team* 2 {:profile-id (:id prof1)})]
|
||||||
|
|
||||||
|
;; Request profile to be deleted
|
||||||
|
(let [params {::th/type :delete-profile
|
||||||
|
::rpc/profile-id (:id prof1)}
|
||||||
|
out (th/command! params)]
|
||||||
|
;; (th/print-result! out)
|
||||||
|
(t/is (= {} (:result out)))
|
||||||
|
(t/is (nil? (:error out))))
|
||||||
|
|
||||||
|
(th/run-pending-tasks!)
|
||||||
|
|
||||||
|
(let [rows (th/db-exec! ["select id,name,deleted_at from team where deleted_at is not null"])]
|
||||||
|
(t/is (= 3 (count rows))))
|
||||||
|
|
||||||
|
;; execute permanent deletion task
|
||||||
|
(let [result (th/run-task! :objects-gc {:min-age 0})]
|
||||||
|
(t/is (= 8 (:processed result))))))
|
||||||
|
|
||||||
|
|
||||||
(t/deftest email-blacklist-1
|
(t/deftest email-blacklist-1
|
||||||
(t/is (false? (email.blacklist/enabled? th/*system*)))
|
(t/is (false? (email.blacklist/enabled? th/*system*)))
|
||||||
(t/is (true? (email.blacklist/enabled? (assoc th/*system* :app.email/blacklist []))))
|
(t/is (true? (email.blacklist/enabled? (assoc th/*system* :app.email/blacklist []))))
|
||||||
|
|
Loading…
Add table
Reference in a new issue