From 2b7bd8fa5cc6bb812b1433137da2e661cc30e02e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 8 Oct 2025 12:55:52 +0200 Subject: [PATCH] :bug: Fix deleted files are accesible --- backend/src/app/db.clj | 4 +-- backend/src/app/features/file_snapshots.clj | 10 +++--- backend/src/app/rpc/commands/files.clj | 22 ++++++++++--- .../backend_tests/rpc_file_snapshot_test.clj | 3 +- backend/test/backend_tests/rpc_file_test.clj | 31 ++++--------------- .../main/data/workspace/notifications.cljs | 15 +++++++++ 6 files changed, 46 insertions(+), 39 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index c2519a3831..4f97f35093 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -379,9 +379,7 @@ (defn is-row-deleted? [{:keys [deleted-at]}] - (and (ct/inst? deleted-at) - (< (inst-ms deleted-at) - (inst-ms (ct/now))))) + (some? deleted-at)) (defn get* "Retrieve a single row from database that matches a simple filters. Do diff --git a/backend/src/app/features/file_snapshots.clj b/backend/src/app/features/file_snapshots.clj index 1c93287d24..cf40a08a79 100644 --- a/backend/src/app/features/file_snapshots.clj +++ b/backend/src/app/features/file_snapshots.clj @@ -90,15 +90,16 @@ FROM snapshots AS c WHERE c.id = ? AND CASE WHEN c.created_by = 'user' - THEN (c.deleted_at IS NULL) + THEN c.deleted_at IS NULL WHEN c.created_by = 'system' - THEN (c.deleted_at IS NULL OR c.deleted_at >= ?::timestamptz) + THEN c.deleted_at IS NULL OR c.deleted_at >= ?::timestamptz END")) (defn get-minimal-snapshot [cfg snapshot-id] (let [now (ct/now)] - (-> (db/get-with-sql cfg [sql:get-snapshot-without-data snapshot-id now]) + (-> (db/get-with-sql cfg [sql:get-snapshot-without-data snapshot-id now] + {::db/remove-deleted false}) (decode-snapshot)))) (def ^:private sql:get-snapshot @@ -115,7 +116,8 @@ "Get snapshot with decoded data" [cfg file-id snapshot-id] (let [now (ct/now)] - (->> (db/get-with-sql cfg [sql:get-snapshot file-id snapshot-id now]) + (->> (db/get-with-sql cfg [sql:get-snapshot file-id snapshot-id now] + {::db/remove-deleted false}) (decode-snapshot) (fdata/resolve-file-data cfg) (fdata/decode-file-data cfg)))) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 39c53d401c..58d6c337fc 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -27,6 +27,7 @@ [app.features.logical-deletion :as ldel] [app.loggers.audit :as-alias audit] [app.loggers.webhooks :as-alias webhooks] + [app.msgbus :as mbus] [app.rpc :as-alias rpc] [app.rpc.commands.projects :as projects] [app.rpc.commands.teams :as teams] @@ -80,8 +81,10 @@ fpr.is_admin, fpr.can_edit from file_profile_rel as fpr + inner join file as f on (f.id = fpr.file_id) where fpr.file_id = ? and fpr.profile_id = ? + and f.deleted_at is null union all select tpr.is_owner, tpr.is_admin, @@ -91,6 +94,7 @@ inner join file as f on (p.id = f.project_id) where f.id = ? and tpr.profile_id = ? + and f.deleted_at is null union all select ppr.is_owner, ppr.is_admin, @@ -98,7 +102,8 @@ from project_profile_rel as ppr inner join file as f on (f.project_id = ppr.project_id) where f.id = ? - and ppr.profile_id = ?") + and ppr.profile_id = ? + and f.deleted_at is null") (defn get-file-permissions [conn profile-id file-id] @@ -730,9 +735,9 @@ (defn- get-file-info [{:keys [::db/conn] :as cfg} {:keys [id] :as params}] - (db/get* conn :file - {:id id} - {::sql/columns [:id]})) + (db/get conn :file + {:id id} + {::sql/columns [:id :deleted-at]})) (sv/defmethod ::get-file-info "Retrieve minimal file info by its ID." @@ -944,7 +949,14 @@ (let [team (teams/get-team conn :profile-id profile-id :file-id id) - file (mark-file-deleted conn team id)] + file (mark-file-deleted conn team id) + msgbus (::mbus/msgbus cfg)] + + (mbus/pub! msgbus + :topic id + :message {:type :file-deleted + :file-id id + :profile-id profile-id}) (rph/with-meta (rph/wrap) {::audit/props {:project-id (:project-id file) diff --git a/backend/test/backend_tests/rpc_file_snapshot_test.clj b/backend/test/backend_tests/rpc_file_snapshot_test.clj index f57406ac27..e2c8248815 100644 --- a/backend/test/backend_tests/rpc_file_snapshot_test.clj +++ b/backend/test/backend_tests/rpc_file_snapshot_test.clj @@ -132,7 +132,6 @@ ;; this will run pending task triggered by deleting user snapshot (th/run-pending-tasks!) - ;; this will (let [res (th/run-task! :objects-gc {:deletion-threshold (cf/get-deletion-delay)})] ;; delete 2 snapshots and 2 file data entries (t/is (= 4 (:processed res)))))))) @@ -179,7 +178,7 @@ (t/is (nil? (:error out))) (t/is (true? (:result out))) - (let [snapshot (th/db-get :file-change {:id (:id snapshot)})] + (let [snapshot (th/db-get :file-change {:id (:id snapshot)} {::db/remove-deleted false})] (t/is (= (:id profile-1) (:locked-by snapshot)))))) (t/testing "delete locked snapshot" diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj index 67c22657a2..f8701a0c1c 100644 --- a/backend/test/backend_tests/rpc_file_test.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -117,29 +117,8 @@ (t/is (nil? (:error out))) (t/is (nil? (:result out))))) + (t/testing "query single file after delete" - (let [data {::th/type :get-file - ::rpc/profile-id (:id prof) - :id file-id - :components-v2 true} - out (th/command! data)] - - - ;; (th/print-result! out) - (t/is (nil? (:error out))) - - (let [result (:result out)] - (t/is (some? (:deleted-at result))) - (t/is (= file-id (:id result))) - (t/is (= "new name" (:name result))) - (t/is (= 1 (count (get-in result [:data :pages])))) - (t/is (nil? (:users result)))))) - - (th/db-update! :file - {:deleted-at (ct/now)} - {:id file-id}) - - (t/testing "query single file after delete and wait" (let [data {::th/type :get-file ::rpc/profile-id (:id prof) :id file-id @@ -959,9 +938,11 @@ :file-id (:id file)} out (th/command! data)] ;; (th/print-result! out) - (t/is (nil? (:error out))) - (let [result (:result out)] - (t/is (= 0 (count result))))) + + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)))) ;; run permanent deletion (should be noop) (let [result (th/run-task! :objects-gc {})] diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index 4ecda05c32..1f678dd197 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -24,6 +24,7 @@ [app.main.data.workspace.layout :as dwly] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.texts :as dwt] + [app.main.router :as rt] [app.util.globals :refer [global]] [app.util.mouse :as mse] [app.util.object :as obj] @@ -38,6 +39,7 @@ (declare handle-presence) (declare handle-pointer-update) (declare handle-file-change) +(declare handle-file-deleted) (declare handle-file-restore) (declare handle-library-change) (declare handle-pointer-send) @@ -129,6 +131,7 @@ :disconnect (handle-presence msg) :pointer-update (handle-pointer-update msg) :file-change (handle-file-change msg) + :file-deleted (handle-file-deleted msg) :file-restore (handle-file-restore msg) :library-change (handle-library-change msg) :notification (dc/handle-notification msg) @@ -266,6 +269,18 @@ :redo-changes (vec changes) :undo-changes []}))))) +(defn handle-file-deleted + [{:keys [file-id] :as msg}] + (ptk/reify ::handle-file-deleted + ptk/WatchEvent + (watch [_ state _] + (let [curr-file-id (:current-file-id state) + team-id (:current-team-id state)] + ;; If the deleted file is the currently open one + (when (= file-id curr-file-id) + (rx/of + (rt/nav :dashboard-recent {:team-id team-id}))))))) + (def ^:private schema:handle-file-restore [:map {:title "handle-file-restore"}