♻️ Remove deleted-at from demo profile creation, use delayed worker task instead

Demo profiles were created with deleted-at set to a future date, causing
conflicts with db/is-row-deleted? which treats any non-nil deleted-at as
deleted. This required ::db/remove-deleted false workarounds scattered
across the codebase.

Now demo profiles are created with deleted_at = NULL (fully active during
their lifetime). A delayed :demo-purge worker task is scheduled at creation
time. When it fires after the configured deletion-delay, it marks the
profile with deleted_at = now() and submits the standard delete-object
cascade (teams -> projects -> files -> media).

- New: app.tasks.demo-purge handler (delayed worker task)
- Modified: demo.clj removes :deleted-at, adds delayed task submission
- Modified: profile.clj removes ::db/remove-deleted false workaround
- Modified: main.clj registers demo-purge handler
This commit is contained in:
Andrey Antukh 2026-06-24 11:16:16 +00:00
parent be5c76cfc9
commit 45a0937266
5 changed files with 102 additions and 5 deletions

View File

@ -391,6 +391,8 @@
:delete-object
(ig/ref :app.tasks.delete-object/handler)
:demo-purge
(ig/ref :app.tasks.demo-purge/handler)
:process-webhook-event
(ig/ref ::webhooks/process-event-handler)
:run-webhook
@ -428,6 +430,9 @@
:app.tasks.delete-object/handler
{::db/pool (ig/ref ::db/pool)}
:app.tasks.demo-purge/handler
{::db/pool (ig/ref ::db/pool)}
:app.tasks.file-gc/handler
{::db/pool (ig/ref ::db/pool)
::sto/storage (ig/ref ::sto/storage)}

View File

@ -9,7 +9,6 @@
(:require
[app.auth :refer [derive-password-weak]]
[app.common.exceptions :as ex]
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@ -18,6 +17,7 @@
[app.rpc.commands.auth :as auth]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
[app.worker :as wrk]
[buddy.core.codecs :as bc]
[buddy.core.nonce :as bn]))
@ -47,12 +47,18 @@
:fullname fullname
:is-active true
:is-demo true
:deleted-at (ct/in-future (cf/get-deletion-delay))
:password (derive-password-weak password)
:props {}}
profile (db/tx-run! cfg (fn [cfg]
(->> (auth/create-profile cfg params)
(auth/create-profile-rels cfg))))]
(wrk/submit! (-> cfg
(assoc ::wrk/task :demo-purge)
(assoc ::wrk/delay (cf/get-deletion-delay))
(assoc ::wrk/params {:profile-id (:id profile)})))
(with-meta {:email email
:password password}
{::audit/profile-id (:id profile)})))

View File

@ -122,9 +122,7 @@
(defn get-profile
"Get profile by id. Throws not-found exception if no profile found."
[conn id & {:as opts}]
;; NOTE: We need to set ::db/remove-deleted to false because demo profiles
;; are created with a set deleted-at value
(-> (db/get-by-id conn :profile id (assoc opts ::db/remove-deleted false))
(-> (db/get-by-id conn :profile id opts)
(decode-row)))
;; --- MUTATION: Update Profile (own)

View File

@ -0,0 +1,41 @@
;; 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 Sucursal en España SL
(ns app.tasks.demo-purge
"Task handler for delayed demo profile deletion. Submitted at demo
creation time with a delay matching the configured deletion-delay."
(:require
[app.common.logging :as l]
[app.common.time :as ct]
[app.db :as db]
[app.worker :as wrk]
[integrant.core :as ig]))
(defmethod ig/assert-key ::handler
[_ params]
(assert (db/pool? (::db/pool params)) "expected a valid database pool"))
(defmethod ig/init-key ::handler
[_ cfg]
(fn [{:keys [props]}]
(let [profile-id (get props :profile-id)
now (ct/now)]
(l/trc :hint "demo-purge" :profile-id (str profile-id))
;; Mark the profile for immediate deletion
(db/tx-run! cfg
(fn [{:keys [::db/conn] :as cfg}]
(db/update! conn :profile
{:deleted-at now}
{:id profile-id}
{::db/return-keys false})
(wrk/submit!
(-> cfg
(assoc ::wrk/task :delete-object)
(assoc ::wrk/params {:object :profile
:deleted-at now
:id profile-id}))))))))

View File

@ -0,0 +1,47 @@
;; 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 Sucursal en España SL
(ns backend-tests.demo-test
(:require
[app.common.time :as ct]
[app.db :as db]
[app.rpc.commands.profile :as profile]
[app.tasks.demo-purge :as demo-purge]
[app.worker :as wrk]
[backend-tests.helpers :as th]
[clojure.test :as t]
[integrant.core :as ig]))
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
(t/deftest demo-profile-created-without-deleted-at
(let [profile (th/create-profile* 999 {:is-demo true})]
(t/is (true? (:is-demo profile)))
(t/is (nil? (:deleted-at profile)))
(t/is (some? (:id profile)))))
(t/deftest get-profile-finds-demo-user-without-override
(let [profile (th/create-profile* 998 {:is-demo true})
found (db/run! th/*pool*
(fn [{:keys [::db/conn]}]
(profile/get-profile conn (:id profile))))]
(t/is (some? found))
(t/is (= (:id profile) (:id found)))))
(t/deftest demo-purge-handler-submits-delete-object
(let [profile (th/create-profile* 996 {:is-demo true})
handler (ig/init-key :app.tasks.demo-purge/handler
{::db/pool th/*pool*})
submitted (atom nil)]
(with-redefs [wrk/submit! (fn [& {:keys [::wrk/task ::wrk/params]}]
(reset! submitted {:task task :params params}))]
(handler {:props {:profile-id (:id profile)
:deleted-at (ct/now)}}))
(t/is (= :delete-object (:task @submitted)))
(t/is (= :profile (:object (:params @submitted))))
(t/is (= (:id profile) (:id (:params @submitted))))
(t/is (some? (:deleted-at (:params @submitted))))))