diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index e165173f23..ea9653f1f1 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -912,13 +912,19 @@ ;; --- MUTATION COMMAND: delete-file -(defn- mark-file-deleted! +(defn- mark-file-deleted [conn file-id] - (db/update! conn :file - {:deleted-at (dt/now)} - {:id file-id} - {::db/return-keys [:id :name :is-shared :deleted-at - :project-id :created-at :modified-at]})) + (let [file (db/update! conn :file + {:deleted-at (dt/now)} + {:id file-id} + {::db/return-keys [:id :name :is-shared :deleted-at + :project-id :created-at :modified-at]})] + (wrk/submit! {::wrk/task :delete-object + ::wrk/conn conn + :object :file + :deleted-at (:deleted-at file) + :id file-id}) + file)) (def ^:private schema:delete-file @@ -929,14 +935,7 @@ (defn- delete-file [{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}] (check-edition-permissions! conn profile-id id) - (let [file (mark-file-deleted! conn id)] - - (wrk/submit! {::wrk/task :delete-object - ::wrk/delay (dt/duration "1m") - ::wrk/conn conn - :object :file - :deleted-at (:deleted-at file) - :id id}) + (let [file (mark-file-deleted conn id)] ;; NOTE: when a file is a shared library, then we proceed to load ;; the whole file, proceed with feature checking and properly execute @@ -951,8 +950,6 @@ :profile-id profile-id :project-id (:project-id file))] - - (-> (cfeat/get-team-enabled-features cf/flags team) (cfeat/check-client-features! (:features params)) (cfeat/check-file-features! (:features file))) diff --git a/backend/src/app/rpc/commands/projects.clj b/backend/src/app/rpc/commands/projects.clj index 29cbeaf514..a8236008e3 100644 --- a/backend/src/app/rpc/commands/projects.clj +++ b/backend/src/app/rpc/commands/projects.clj @@ -7,6 +7,7 @@ (ns app.rpc.commands.projects (:require [app.common.data.macros :as dm] + [app.common.exceptions :as ex] [app.common.spec :as us] [app.db :as db] [app.db.sql :as-alias sql] @@ -245,32 +246,37 @@ ;; --- MUTATION: Delete Project +(defn- delete-project + [conn project-id] + (let [project (db/update! conn :project + {:deleted-at (dt/now)} + {:id project-id} + {::db/return-keys true})] + + (when (:is-default project) + (ex/raise :type :validation + :code :non-deletable-project + :hint "impossible to delete default project")) + + (wrk/submit! {::wrk/task :delete-object + ::wrk/conn conn + :object :project + :deleted-at (:deleted-at project) + :id project-id}) + + project)) + (s/def ::delete-project (s/keys :req [::rpc/profile-id] :req-un [::id])) -;; TODO: right now, we just don't allow delete default projects, in a -;; future we need to ensure raise a correct exception signaling that -;; this is not allowed. - (sv/defmethod ::delete-project {::doc/added "1.18" ::webhooks/event? true} [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}] (db/with-atomic [conn pool] (check-edition-permissions! conn profile-id id) - (let [project (db/update! conn :project - {:deleted-at (dt/now)} - {:id id :is-default false} - {::db/return-keys true})] - - (wrk/submit! {::wrk/task :delete-object - ::wrk/delay (dt/duration "1m") - ::wrk/conn conn - :object :project - :deleted-at (:deleted-at project) - :id id}) - + (let [project (delete-project conn id)] (rph/with-meta (rph/wrap) {::audit/props {:team-id (:team-id project) :name (:name project) diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index dabf6c8487..4cc75de5a4 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -517,38 +517,44 @@ ;; --- Mutation: Delete Team +(defn- delete-team + "Mark a team for deletion" + [conn team-id] + + (let [deleted-at (dt/now) + team (db/update! conn :team + {:deleted-at deleted-at} + {:id team-id} + {::db/return-keys true})] + + (when (:is-default team) + (ex/raise :type :validation + :code :non-deletable-team + :hint "impossible to delete default team")) + + (wrk/submit! {::wrk/task :delete-object + ::wrk/conn conn + :object :team + :deleted-at deleted-at + :id team-id}) + team)) + (s/def ::delete-team (s/keys :req [::rpc/profile-id] :req-un [::id])) -;; TODO: right now just don't allow delete default team, in future it -;; should raise a specific exception for signal that this action is -;; not allowed. - (sv/defmethod ::delete-team {::doc/added "1.17"} [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}] (db/with-atomic [conn pool] - (let [perms (get-permissions conn profile-id id) - deleted-at (dt/now)] - + (let [perms (get-permissions conn profile-id id)] (when-not (:is-owner perms) (ex/raise :type :validation :code :only-owner-can-delete-team)) - (db/update! conn :team - {:deleted-at deleted-at} - {:id id :is-default false}) - - (wrk/submit! {::wrk/task :delete-object - ::wrk/delay (dt/duration "1m") - ::wrk/conn conn - :object :team - :deleted-at deleted-at - :id id}) + (delete-team conn id) nil))) - ;; --- Mutation: Team Update Role (s/def ::team-id ::us/uuid) diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 622dc840ec..87ff5cd81d 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -30,6 +30,8 @@ [app.rpc.commands.files-snapshot :as fsnap] [app.rpc.commands.management :as mgmt] [app.rpc.commands.profile :as profile] + [app.rpc.commands.projects :as projects] + [app.rpc.commands.teams :as teams] [app.srepl.fixes :as fixes] [app.srepl.helpers :as h] [app.util.blob :as blob] @@ -475,80 +477,99 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; RESTORE DELETED OBJECTS +;; DELETE/RESTORE OBJECTS (WITH CASCADE, SOFT) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- restore-file* + [{:keys [::db/conn]} file-id] + (db/update! conn :file + {:deleted-at nil + :has-media-trimmed false} + {:id file-id}) + + ;; Fragments are not handled here because they + ;; use the database cascade operation and they + ;; are not marked for deletion with objects-gc + ;; task + + (db/update! conn :file-media-object + {:deleted-at nil} + {:file-id file-id}) + + ;; Mark thumbnails to be deleted + (db/update! conn :file-thumbnail + {:deleted-at nil} + {:file-id file-id}) + + (db/update! conn :file-tagged-object-thumbnail + {:deleted-at nil} + {:file-id file-id})) + +(defn- restore-project* + [{:keys [::db/conn]} project-id] + + (db/update! conn :project + {:deleted-at nil} + {:id project-id}) + + (doseq [{:keys [id]} (db/query conn :file + {:project-id project-id} + {::db/columns [:id]})] + (restore-file* conn id))) + +(defn- restore-team* + [{:keys [::db/conn]} team-id] + (db/update! conn :team + {:deleted-at nil} + {:id team-id}) + + (db/update! conn :team-font-variant + {:deleted-at nil} + {:team-id team-id}) + + (doseq [{:keys [id]} (db/query conn :project + {:team-id team-id} + {::db/columns [:id]})] + (restore-project* conn id))) + (defn restore-deleted-team! "Mark a team and all related objects as not deleted" [team-id] (let [team-id (h/parse-uuid team-id)] - (db/tx-run! main/system - (fn [{:keys [::db/conn]}] - (db/update! conn :team-font-variant - {:deleted-at nil} - {:team-id team-id}) - - (doseq [project (db/update! conn :project - {:deleted-at nil} - {:team-id team-id} - {::db/return-keys [:id] - ::db/many true})] - - (doseq [file (db/update! conn :file - {:deleted-at nil - :has-media-trimmed false} - {:project-id (:id project)} - {::db/return-keys [:id] - ::db/many true})] - - ;; Fragments are not handled here because they - ;; use the database cascade operation and they - ;; are not marked for deletion with objects-gc - ;; task - - (db/update! conn :file-media-object - {:deleted-at nil} - {:file-id (:id file)}) - - ;; Mark thumbnails to be deleted - (db/update! conn :file-thumbnail - {:deleted-at nil} - {:file-id (:id file)}) - - (db/update! conn :file-tagged-object-thumbnail - {:deleted-at nil} - {:file-id (:id file)}))))))) - + (db/tx-run! main/system restore-team* team-id))) (defn restore-deleted-project! "Mark a project and all related objects as not deleted" [project-id] (let [project-id (h/parse-uuid project-id)] - (db/tx-run! main/system - (fn [{:keys [::db/conn]}] - (doseq [file (db/update! conn :file - {:deleted-at nil - :has-media-trimmed false} - {:project-id project-id} - {::db/return-keys [:id] - ::db/many true})] + (db/tx-run! main/system restore-project* project-id))) - ;; Fragments are not handled here because they use - ;; the database cascade operation and they are not - ;; marked for deletion with objects-gc task +(defn restore-deleted-file! + "Mark a file and all related objects as not deleted" + [file-id] + (let [file-id (h/parse-uuid file-id)] + (db/tx-run! main/system restore-file* file-id))) - (db/update! conn :file-media-object - {:deleted-at nil} - {:file-id (:id file)}) +(defn delete-team! + "Mark a team for deletion" + [team-id] + (let [team-id (h/parse-uuid team-id)] + (db/tx-run! main/system (fn [{:keys [::db/conn]}] + (#'teams/delete-team conn team-id))))) - ;; Mark thumbnails to be deleted - (db/update! conn :file-thumbnail - {:deleted-at nil} - {:file-id (:id file)}) +(defn delete-project! + "Mark a project for deletion" + [project-id] + (let [project-id (h/parse-uuid project-id)] + (db/tx-run! main/system (fn [{:keys [::db/conn]}] + (#'projects/delete-project conn project-id))))) - (db/update! conn :file-tagged-object-thumbnail - {:deleted-at nil} - {:file-id (:id file)})))))) +(defn delete-file! + "Mark a project for deletion" + [file-id] + (let [file-id (h/parse-uuid file-id)] + (db/tx-run! main/system (fn [{:keys [::db/conn]}] + (#'files/mark-file-deleted conn file-id))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; MISC diff --git a/backend/src/app/tasks/delete_object.clj b/backend/src/app/tasks/delete_object.clj index f0a60d30a8..bd954cd9da 100644 --- a/backend/src/app/tasks/delete_object.clj +++ b/backend/src/app/tasks/delete_object.clj @@ -17,7 +17,7 @@ (defmethod delete-object :file [{:keys [::db/conn]} {:keys [id deleted-at]}] - (l/trc :hint "marking for deletion" :rel "file" :id id) + (l/trc :hint "marking for deletion" :rel "file" :id (str id)) ;; Mark file media objects to be deleted (db/update! conn :file-media-object {:deleted-at deleted-at} @@ -34,7 +34,7 @@ (defmethod delete-object :project [{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}] - (l/trc :hint "marking for deletion" :rel "project" :id id) + (l/trc :hint "marking for deletion" :rel "project" :id (str id)) (doseq [file (db/update! conn :file {:deleted-at deleted-at} {:project-id id} @@ -44,7 +44,7 @@ (defmethod delete-object :team [{:keys [::db/conn] :as cfg} {:keys [id deleted-at]}] - (l/trc :hint "marking for deletion" :rel "team" :id id) + (l/trc :hint "marking for deletion" :rel "team" :id (str id)) (db/update! conn :team-font-variant {:deleted-at deleted-at} {:team-id id}) diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index 12d76785ee..cb399e70f2 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -219,7 +219,7 @@ ([params] (mark-file-deleted* *system* params)) ([conn {:keys [id] :as params}] - (#'files/mark-file-deleted! conn id))) + (#'files/mark-file-deleted conn id))) (defn create-team* ([i params] (create-team* *system* i params))