mirror of
https://github.com/penpot/penpot.git
synced 2026-09-06 20:18:39 +00:00
🐛 Cascade profile deletion in objects-gc task (#11395)
The objects-gc task was performing a hard delete on profiles without cascading the soft-delete to owned teams, projects, and files. This left orphaned objects that were never cleaned up. Now the task invokes delete-object before the hard delete, ensuring all owned resources are properly marked for deletion and cleaned up in subsequent GC iterations. AI-assisted-by: qwen3.7-plus
This commit is contained in:
parent
6d9f411fab
commit
810f8ef671
@ -13,6 +13,7 @@
|
|||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.features.fdata :as fdata]
|
[app.features.fdata :as fdata]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
|
[app.tasks.delete-object :as dobj]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
(def ^:private sql:get-profiles
|
(def ^:private sql:get-profiles
|
||||||
@ -33,6 +34,11 @@
|
|||||||
;; 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))
|
||||||
|
|
||||||
|
;; Cascade soft-delete to owned teams, projects, files, etc.
|
||||||
|
(dobj/delete-object cfg {:object :profile
|
||||||
|
:id id
|
||||||
|
:deleted-at timestamp})
|
||||||
|
|
||||||
(let [affected (-> (db/delete! conn :profile {:id id})
|
(let [affected (-> (db/delete! conn :profile {:id id})
|
||||||
(db/get-update-count))]
|
(db/get-update-count))]
|
||||||
(+ total affected)))
|
(+ total affected)))
|
||||||
|
|||||||
@ -424,6 +424,38 @@
|
|||||||
(let [count-after (:count (th/db-exec-one! ["SELECT count(*) FROM http_session_v2 WHERE profile_id = ?" (:id prof)]))]
|
(let [count-after (:count (th/db-exec-one! ["SELECT count(*) FROM http_session_v2 WHERE profile_id = ?" (:id prof)]))]
|
||||||
(t/is (= 0 count-after)))))
|
(t/is (= 0 count-after)))))
|
||||||
|
|
||||||
|
(t/deftest profile-deletion-via-gc-cascades
|
||||||
|
(let [prof (th/create-profile* 1)
|
||||||
|
file (th/create-file* 1 {:profile-id (:id prof)
|
||||||
|
:project-id (:default-project-id prof)
|
||||||
|
:is-shared false})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
project-id (:default-project-id prof)
|
||||||
|
file-id (:id file)
|
||||||
|
|
||||||
|
deleted-at (ct/minus (ct/now) (ct/duration {:days 1}))]
|
||||||
|
|
||||||
|
(th/db-update! :profile
|
||||||
|
{:deleted-at deleted-at}
|
||||||
|
{:id (:id prof)})
|
||||||
|
|
||||||
|
(let [team-before (th/db-get :team {:id team-id} {::db/remove-deleted false})]
|
||||||
|
(t/is (nil? (:deleted-at team-before))))
|
||||||
|
|
||||||
|
(let [result (th/run-task! :objects-gc {:min-age 0})]
|
||||||
|
(t/is (pos? (:processed result))))
|
||||||
|
|
||||||
|
(let [profile-after (th/db-get :profile {:id (:id prof)} {::db/remove-deleted false})]
|
||||||
|
(t/is (nil? profile-after)))
|
||||||
|
|
||||||
|
(let [team-after (th/db-get :team {:id team-id} {::db/remove-deleted false})]
|
||||||
|
(t/is (nil? team-after)))
|
||||||
|
|
||||||
|
(let [project-after (th/db-get :project {:id project-id} {::db/remove-deleted false})]
|
||||||
|
(t/is (nil? project-after)))
|
||||||
|
|
||||||
|
(let [file-after (th/db-get :file {:id file-id} {::db/remove-deleted false})]
|
||||||
|
(t/is (nil? file-after)))))
|
||||||
|
|
||||||
(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*)))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user