mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 13:56:39 +02:00
✨ Add permission checking to file snapshot rpc methods
This commit is contained in:
parent
b4f868be91
commit
5f4af76d28
9 changed files with 308 additions and 202 deletions
|
@ -1745,8 +1745,8 @@
|
|||
(fn [system]
|
||||
(binding [*system* system]
|
||||
(when (string? label)
|
||||
(fsnap/take-file-snapshot! system {:file-id file-id
|
||||
:label (str "migration/" label)}))
|
||||
(fsnap/create-file-snapshot! system nil file-id (str "migration/" label)))
|
||||
|
||||
(let [file (get-file system file-id)
|
||||
file (process-file! system file :validate? validate?)]
|
||||
|
||||
|
|
|
@ -29,17 +29,13 @@
|
|||
"SELECT id, label, revn, created_at, created_by, profile_id
|
||||
FROM file_change
|
||||
WHERE file_id = ?
|
||||
AND created_at < ?
|
||||
AND data IS NOT NULL
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?")
|
||||
LIMIT 20")
|
||||
|
||||
(defn get-file-snapshots
|
||||
[{:keys [::db/conn]} {:keys [file-id limit start-at]
|
||||
:or {limit Long/MAX_VALUE}}]
|
||||
(let [start-at (or start-at (dt/now))
|
||||
limit (min limit 20)]
|
||||
(db/exec! conn [sql:get-file-snapshots file-id start-at limit])))
|
||||
[conn file-id]
|
||||
(db/exec! conn [sql:get-file-snapshots file-id]))
|
||||
|
||||
(def ^:private schema:get-file-snapshots
|
||||
[:map {:title "get-file-snapshots"}
|
||||
|
@ -48,29 +44,121 @@
|
|||
(sv/defmethod ::get-file-snapshots
|
||||
{::doc/added "1.20"
|
||||
::sm/params schema:get-file-snapshots}
|
||||
[cfg params]
|
||||
(db/run! cfg get-file-snapshots params))
|
||||
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
||||
(db/run! cfg (fn [{:keys [::db/conn]}]
|
||||
(files/check-read-permissions! conn profile-id file-id)
|
||||
(get-file-snapshots conn file-id))))
|
||||
|
||||
(defn- get-file
|
||||
[cfg file-id]
|
||||
(let [file (->> (db/get cfg :file {:id file-id})
|
||||
(feat.fdata/resolve-file-data cfg))]
|
||||
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
|
||||
(-> file
|
||||
(update :data blob/decode)
|
||||
(update :data feat.fdata/process-pointers deref)
|
||||
(update :data feat.fdata/process-objects (partial into {}))
|
||||
(update :data blob/encode)))))
|
||||
|
||||
(defn- generate-snapshot-label
|
||||
[]
|
||||
(let [ts (-> (dt/now)
|
||||
(dt/format-instant)
|
||||
(str/replace #"[T:\.]" "-")
|
||||
(str/rtrim "Z"))]
|
||||
(str "snapshot-" ts)))
|
||||
|
||||
(defn create-file-snapshot!
|
||||
[cfg profile-id file-id label]
|
||||
(let [file (-> (get-file cfg file-id)
|
||||
(update :data
|
||||
(fn [data]
|
||||
(-> data
|
||||
(blob/decode)
|
||||
(assoc :id file-id)))))
|
||||
|
||||
;; NOTE: final user never can provide label as `:system`
|
||||
;; keyword because the validator implies label always as
|
||||
;; string; keyword is used for signal a special case
|
||||
created-by
|
||||
(if (= label :system)
|
||||
"system"
|
||||
"user")
|
||||
|
||||
label
|
||||
(if (= label :system)
|
||||
(str "internal/snapshot/" (:revn file))
|
||||
(or label (generate-snapshot-label)))
|
||||
|
||||
snapshot-id
|
||||
(uuid/next)
|
||||
|
||||
snapshot-data
|
||||
(-> (:data file)
|
||||
(feat.fdata/process-pointers deref)
|
||||
(feat.fdata/process-objects (partial into {}))
|
||||
(blob/encode))]
|
||||
|
||||
(l/debug :hint "creating file snapshot"
|
||||
:file-id (str file-id)
|
||||
:id (str snapshot-id)
|
||||
:label label)
|
||||
|
||||
(db/insert! cfg :file-change
|
||||
{:id snapshot-id
|
||||
:revn (:revn file)
|
||||
:data snapshot-data
|
||||
:version (:version file)
|
||||
:features (:features file)
|
||||
:profile-id profile-id
|
||||
:file-id (:id file)
|
||||
:label label
|
||||
:created-by created-by}
|
||||
{::db/return-keys false})
|
||||
|
||||
{:id snapshot-id :label label}))
|
||||
|
||||
(def ^:private schema:create-file-snapshot
|
||||
[:map
|
||||
[:file-id ::sm/uuid]
|
||||
[:label {:optional true} :string]])
|
||||
|
||||
(sv/defmethod ::create-file-snapshot
|
||||
{::doc/added "1.20"
|
||||
::sm/params schema:create-file-snapshot}
|
||||
[cfg {:keys [::rpc/profile-id file-id label]}]
|
||||
(db/tx-run! cfg
|
||||
(fn [{:keys [::db/conn] :as cfg}]
|
||||
(files/check-edition-permissions! conn profile-id file-id)
|
||||
(create-file-snapshot! cfg profile-id file-id label))))
|
||||
|
||||
(defn restore-file-snapshot!
|
||||
[{:keys [::db/conn ::mbus/msgbus] :as cfg} {:keys [file-id id]}]
|
||||
[{:keys [::db/conn ::mbus/msgbus] :as cfg} file-id snapshot-id]
|
||||
(let [storage (sto/resolve cfg {::db/reuse-conn true})
|
||||
file (files/get-minimal-file conn file-id {::db/for-update true})
|
||||
vern (rand-int Integer/MAX_VALUE)
|
||||
snapshot (db/get* conn :file-change
|
||||
{:file-id file-id
|
||||
:id id}
|
||||
:id snapshot-id}
|
||||
{::db/for-share true})]
|
||||
|
||||
(when-not snapshot
|
||||
(ex/raise :type :not-found
|
||||
:code :snapshot-not-found
|
||||
:hint "unable to find snapshot with the provided label"
|
||||
:id id
|
||||
:snapshot-id snapshot-id
|
||||
:file-id file-id))
|
||||
|
||||
;; (when (= (:revn snapshot) (:revn file))
|
||||
;; (ex/raise :type :validation
|
||||
;; :code :snapshot-identical-to-file
|
||||
;; :hint "you can't restore a snapshot that is identical to a file"
|
||||
;; :snapshot-id snapshot-id
|
||||
;; :file-id file-id))
|
||||
|
||||
(let [snapshot (feat.fdata/resolve-file-data cfg snapshot)]
|
||||
(when-not (:data snapshot)
|
||||
(ex/raise :type :precondition
|
||||
(ex/raise :type :validation
|
||||
:code :snapshot-without-data
|
||||
:hint "snapshot has no data"
|
||||
:label (:label snapshot)
|
||||
|
@ -123,125 +211,77 @@
|
|||
{:id (:id snapshot)
|
||||
:label (:label snapshot)})))
|
||||
|
||||
(def ^:private
|
||||
schema:restore-file-snapshot
|
||||
[:and
|
||||
[:map
|
||||
[:file-id ::sm/uuid]
|
||||
[:id {:optional true} ::sm/uuid]]
|
||||
[::sm/contains-any #{:id :label}]])
|
||||
(def ^:private schema:restore-file-snapshot
|
||||
[:map {:title "restore-file-snapshot"}
|
||||
[:file-id ::sm/uuid]
|
||||
[:id ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::restore-file-snapshot
|
||||
{::doc/added "1.20"
|
||||
::sm/params schema:restore-file-snapshot}
|
||||
[cfg params]
|
||||
(db/tx-run! cfg restore-file-snapshot! params))
|
||||
|
||||
(defn- get-file
|
||||
[cfg file-id]
|
||||
(let [file (->> (db/get cfg :file {:id file-id})
|
||||
(feat.fdata/resolve-file-data cfg))]
|
||||
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
|
||||
(-> file
|
||||
(update :data blob/decode)
|
||||
(update :data feat.fdata/process-pointers deref)
|
||||
(update :data feat.fdata/process-objects (partial into {}))
|
||||
(update :data blob/encode)))))
|
||||
|
||||
(defn- generate-snapshot-label
|
||||
[]
|
||||
(let [ts (-> (dt/now)
|
||||
(dt/format-instant)
|
||||
(str/replace #"[T:\.]" "-")
|
||||
(str/rtrim "Z"))]
|
||||
(str "snapshot-" ts)))
|
||||
|
||||
(defn take-file-snapshot!
|
||||
[cfg {:keys [file-id label ::rpc/profile-id]}]
|
||||
(let [label (or label (generate-snapshot-label))
|
||||
file (-> (get-file cfg file-id)
|
||||
(update :data
|
||||
(fn [data]
|
||||
(-> data
|
||||
(blob/decode)
|
||||
(assoc :id file-id)))))
|
||||
|
||||
snapshot-id
|
||||
(uuid/next)
|
||||
|
||||
snapshot-data
|
||||
(-> (:data file)
|
||||
(feat.fdata/process-pointers deref)
|
||||
(feat.fdata/process-objects (partial into {}))
|
||||
(blob/encode))]
|
||||
|
||||
(l/debug :hint "creating file snapshot"
|
||||
:file-id (str file-id)
|
||||
:id (str snapshot-id)
|
||||
:label label)
|
||||
|
||||
(db/insert! cfg :file-change
|
||||
{:id snapshot-id
|
||||
:revn (:revn file)
|
||||
:data snapshot-data
|
||||
:version (:version file)
|
||||
:features (:features file)
|
||||
:profile-id profile-id
|
||||
:file-id (:id file)
|
||||
:label label
|
||||
:created-by "user"}
|
||||
{::db/return-keys false})
|
||||
|
||||
{:id snapshot-id :label label}))
|
||||
|
||||
(def ^:private schema:take-file-snapshot
|
||||
[:map
|
||||
[:file-id ::sm/uuid]
|
||||
[:label {:optional true} :string]])
|
||||
|
||||
(sv/defmethod ::take-file-snapshot
|
||||
{::doc/added "1.20"
|
||||
::sm/params schema:take-file-snapshot}
|
||||
[cfg params]
|
||||
(db/tx-run! cfg take-file-snapshot! params))
|
||||
[cfg {:keys [::rpc/profile-id file-id id] :as params}]
|
||||
(db/tx-run! cfg
|
||||
(fn [{:keys [::db/conn] :as cfg}]
|
||||
(files/check-edition-permissions! conn profile-id file-id)
|
||||
(create-file-snapshot! cfg profile-id file-id :system)
|
||||
(restore-file-snapshot! cfg file-id id))))
|
||||
|
||||
(def ^:private schema:update-file-snapshot
|
||||
[:map {:title "update-file-snapshot"}
|
||||
[:id ::sm/uuid]
|
||||
[:label ::sm/text]])
|
||||
|
||||
(defn update-file-snapshot!
|
||||
[{:keys [::db/conn] :as cfg} {:keys [id label]}]
|
||||
(let [result
|
||||
(db/update! conn :file-change
|
||||
{:label label
|
||||
:created-by "user"}
|
||||
{:id id}
|
||||
{::db/return-keys true})]
|
||||
(defn- update-file-snapshot!
|
||||
[conn snapshot-id label]
|
||||
(-> (db/update! conn :file-change
|
||||
{:label label
|
||||
:created-by "user"}
|
||||
{:id snapshot-id}
|
||||
{::db/return-keys true})
|
||||
(dissoc :data :features)))
|
||||
|
||||
(select-keys result [:id :label :revn :created-at :profile-id :created-by])))
|
||||
(defn- get-snapshot
|
||||
"Get a minimal snapshot from database and lock for update"
|
||||
[conn id]
|
||||
(db/get conn :file-change
|
||||
{:id id}
|
||||
{::sql/columns [:id :file-id :created-by]
|
||||
::db/for-update true}))
|
||||
|
||||
(sv/defmethod ::update-file-snapshot
|
||||
{::doc/added "1.20"
|
||||
::sm/params schema:update-file-snapshot}
|
||||
[cfg params]
|
||||
(db/tx-run! cfg update-file-snapshot! params))
|
||||
[cfg {:keys [::rpc/profile-id id label]}]
|
||||
(db/tx-run! cfg
|
||||
(fn [{:keys [::db/conn]}]
|
||||
(let [snapshot (get-snapshot conn id)]
|
||||
(files/check-edition-permissions! conn profile-id (:file-id snapshot))
|
||||
(update-file-snapshot! conn id label)))))
|
||||
|
||||
(def ^:private schema:remove-file-snapshot
|
||||
[:map {:title "remove-file-snapshot"}
|
||||
[:id ::sm/uuid]])
|
||||
|
||||
(defn remove-file-snapshot!
|
||||
[{:keys [::db/conn] :as cfg} {:keys [id]}]
|
||||
(defn- delete-file-snapshot!
|
||||
[conn snapshot-id]
|
||||
(db/delete! conn :file-change
|
||||
{:id id :created-by "user"}
|
||||
{:id snapshot-id}
|
||||
{::db/return-keys false})
|
||||
nil)
|
||||
|
||||
(sv/defmethod ::remove-file-snapshot
|
||||
(sv/defmethod ::delete-file-snapshot
|
||||
{::doc/added "1.20"
|
||||
::sm/params schema:remove-file-snapshot}
|
||||
[cfg params]
|
||||
(db/tx-run! cfg remove-file-snapshot! params))
|
||||
[cfg {:keys [::rpc/profile-id id]}]
|
||||
(db/tx-run! cfg
|
||||
(fn [{:keys [::db/conn]}]
|
||||
(let [snapshot (get-snapshot conn id)]
|
||||
(files/check-edition-permissions! conn profile-id (:file-id snapshot))
|
||||
|
||||
(when (not= (:created-by snapshot) "user")
|
||||
(ex/raise :type :validation
|
||||
:code :system-snapshots-cant-be-deleted
|
||||
:snapshot-id id
|
||||
:profile-id profile-id))
|
||||
|
||||
(delete-file-snapshot! conn id)))))
|
||||
|
|
|
@ -122,22 +122,19 @@
|
|||
WHERE file_id = ANY(?)
|
||||
AND id IS NOT NULL")
|
||||
|
||||
(defn get-file-snapshots
|
||||
(defn search-file-snapshots
|
||||
"Get a seq parirs of file-id and snapshot-id for a set of files
|
||||
and specified label"
|
||||
[conn label ids]
|
||||
[conn file-ids label]
|
||||
(db/exec! conn [sql:snapshots-with-file label
|
||||
(db/create-array conn "uuid" ids)]))
|
||||
(db/create-array conn "uuid" file-ids)]))
|
||||
|
||||
(defn take-team-snapshot!
|
||||
[system team-id label]
|
||||
(let [conn (db/get-connection system)]
|
||||
(->> (feat.comp-v2/get-and-lock-team-files conn team-id)
|
||||
(map (fn [file-id]
|
||||
{:file-id file-id
|
||||
:label label}))
|
||||
(reduce (fn [result params]
|
||||
(fsnap/take-file-snapshot! conn params)
|
||||
(reduce (fn [result file-id]
|
||||
(fsnap/create-file-snapshot! system nil file-id label)
|
||||
(inc result))
|
||||
0))))
|
||||
|
||||
|
@ -147,7 +144,7 @@
|
|||
ids (->> (feat.comp-v2/get-and-lock-team-files conn team-id)
|
||||
(into #{}))
|
||||
|
||||
snap (get-file-snapshots conn label ids)
|
||||
snap (search-file-snapshots conn ids label)
|
||||
|
||||
ids' (into #{} (map :file-id) snap)
|
||||
team (-> (feat.comp-v2/get-team conn team-id)
|
||||
|
@ -157,8 +154,8 @@
|
|||
(throw (RuntimeException. "no uniform snapshot available")))
|
||||
|
||||
(feat.comp-v2/update-team! conn team)
|
||||
(reduce (fn [result params]
|
||||
(fsnap/restore-file-snapshot! conn params)
|
||||
(reduce (fn [result {:keys [file-id id]}]
|
||||
(fsnap/restore-file-snapshot! system file-id id)
|
||||
(inc result))
|
||||
0
|
||||
snap)))
|
||||
|
@ -167,7 +164,7 @@
|
|||
[system file-id update-fn & {:keys [label validate? with-libraries?] :or {validate? true} :as opts}]
|
||||
|
||||
(when (string? label)
|
||||
(fsnap/take-file-snapshot! system {:file-id file-id :label label}))
|
||||
(fsnap/create-file-snapshot! system nil file-id label))
|
||||
|
||||
(let [conn (db/get-connection system)
|
||||
file (get-file system file-id opts)
|
||||
|
|
|
@ -311,28 +311,25 @@
|
|||
collectable file-changes entry."
|
||||
[& {:keys [file-id label]}]
|
||||
(let [file-id (h/parse-uuid file-id)]
|
||||
(db/tx-run! main/system fsnap/take-file-snapshot! {:file-id file-id :label label})))
|
||||
(db/tx-run! main/system fsnap/create-file-snapshot! {:file-id file-id :label label})))
|
||||
|
||||
(defn restore-file-snapshot!
|
||||
[file-id label]
|
||||
(let [file-id (h/parse-uuid file-id)]
|
||||
(db/tx-run! main/system
|
||||
(fn [{:keys [::db/conn] :as system}]
|
||||
(when-let [snapshot (->> (h/get-file-snapshots conn label #{file-id})
|
||||
(when-let [snapshot (->> (h/search-file-snapshots conn #{file-id} label)
|
||||
(map :id)
|
||||
(first))]
|
||||
(fsnap/restore-file-snapshot! system
|
||||
{:id (:id snapshot)
|
||||
:file-id file-id}))))))
|
||||
(fsnap/restore-file-snapshot! system file-id (:id snapshot)))))))
|
||||
|
||||
(defn list-file-snapshots!
|
||||
[file-id & {:keys [limit]}]
|
||||
[file-id & {:as _}]
|
||||
(let [file-id (h/parse-uuid file-id)]
|
||||
(db/tx-run! main/system
|
||||
(fn [system]
|
||||
(let [params {:file-id file-id :limit limit}]
|
||||
(->> (fsnap/get-file-snapshots system (d/without-nils params))
|
||||
(print-table [:label :id :revn :created-at])))))))
|
||||
(fn [{:keys [::db/conn]}]
|
||||
(->> (fsnap/get-file-snapshots conn file-id)
|
||||
(print-table [:label :id :revn :created-at]))))))
|
||||
|
||||
(defn take-team-snapshot!
|
||||
[team-id & {:keys [label rollback?] :or {rollback? true}}]
|
||||
|
|
134
backend/test/backend_tests/rpc_file_snapshot_test.clj
Normal file
134
backend/test/backend_tests/rpc_file_snapshot_test.clj
Normal file
|
@ -0,0 +1,134 @@
|
|||
;; 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 backend-tests.rpc-file-snapshot-test
|
||||
(:require
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.thumbnails :as thc]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.db :as db]
|
||||
[app.db.sql :as sql]
|
||||
[app.http :as http]
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.storage :as sto]
|
||||
[app.util.time :as dt]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.test :as t]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(t/use-fixtures :once th/state-init)
|
||||
(t/use-fixtures :each th/database-reset)
|
||||
|
||||
(defn- update-file!
|
||||
[& {:keys [profile-id file-id changes revn] :or {revn 0}}]
|
||||
(let [params {::th/type :update-file
|
||||
::rpc/profile-id profile-id
|
||||
:id file-id
|
||||
:session-id (uuid/random)
|
||||
:revn revn
|
||||
:vern 0
|
||||
:features cfeat/supported-features
|
||||
:changes changes}
|
||||
out (th/command! params)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(:result out)))
|
||||
|
||||
(t/deftest generic-ops
|
||||
(let [profile (th/create-profile* 1 {:is-active true})
|
||||
team-id (:default-team-id profile)
|
||||
proj-id (:default-project-id profile)
|
||||
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id proj-id
|
||||
:is-shared false})
|
||||
snapshot-id (volatile! nil)]
|
||||
|
||||
(t/testing "create snapshot"
|
||||
(let [params {::th/type :create-file-snapshot
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:label "label1"}
|
||||
out (th/command! params)]
|
||||
;; (th/print-result! out)
|
||||
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (= "label1" (:label result)))
|
||||
(t/is (uuid? (:id result)))
|
||||
(vswap! snapshot-id (constantly (:id result))))))
|
||||
|
||||
(t/testing "list snapshots"
|
||||
(let [params {::th/type :get-file-snapshots
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)}
|
||||
out (th/command! params)]
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [[row :as result] (:result out)]
|
||||
(t/is (= 1 (count result)))
|
||||
(t/is (= "label1" (:label row)))
|
||||
(t/is (uuid? (:id row)))
|
||||
(t/is (= @snapshot-id (:id row)))
|
||||
(t/is (= 0 (:revn row)))
|
||||
(t/is (= (:id profile) (:profile-id row))))))
|
||||
|
||||
(t/testing "restore snapshot"
|
||||
(let [params {::th/type :restore-file-snapshot
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:id @snapshot-id}
|
||||
out (th/command! params)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
|
||||
(let [result (:result out)]
|
||||
(t/is (= "label1" (:label result)))
|
||||
(t/is (uuid? (:id result)))))
|
||||
|
||||
(let [[row1 row2 :as rows]
|
||||
(th/db-query :file-change
|
||||
{:file-id (:id file)}
|
||||
{:order-by [:created-at]})]
|
||||
(t/is (= 2 (count rows)))
|
||||
(t/is (= "user" (:created-by row1)))
|
||||
(t/is (= "system" (:created-by row2)))))
|
||||
|
||||
(t/testing "delete snapshot"
|
||||
(let [[row1 row2 :as rows]
|
||||
(th/db-query :file-change
|
||||
{:file-id (:id file)}
|
||||
{:order-by [:created-at]})]
|
||||
|
||||
(t/testing "delete user created snapshot"
|
||||
(let [params {::th/type :delete-file-snapshot
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:id (:id row1)}
|
||||
out (th/command! params)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(t/is (nil? (:result out)))))
|
||||
|
||||
(t/testing "delete system created snapshot"
|
||||
(let [params {::th/type :delete-file-snapshot
|
||||
::rpc/profile-id (:id profile)
|
||||
:file-id (:id file)
|
||||
:id (:id row2)}
|
||||
out (th/command! params)]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(let [error (:error out)
|
||||
data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type data) :validation))
|
||||
(t/is (= (:code data) :system-snapshots-cant-be-deleted)))))))))
|
||||
|
|
@ -33,7 +33,7 @@ test("Save and restore version", async ({ page }) => {
|
|||
await page.getByLabel("History (Alt+H)").click();
|
||||
|
||||
await workspacePage.mockRPC(
|
||||
"take-file-snapshot",
|
||||
"create-file-snapshot",
|
||||
"workspace/versions-take-snapshot-1.json",
|
||||
);
|
||||
|
||||
|
@ -57,7 +57,11 @@ test("Save and restore version", async ({ page }) => {
|
|||
await page.getByRole("textbox").fill("INIT");
|
||||
await page.getByRole("textbox").press("Enter");
|
||||
|
||||
await page.getByTestId("version(INIT)").getByRole("button").click();
|
||||
await page
|
||||
.locator("li")
|
||||
.filter({ hasText: "INIT" })
|
||||
.getByRole("button")
|
||||
.click();
|
||||
await page.getByRole("button", { name: "Restore" }).click();
|
||||
|
||||
await workspacePage.mockRPC(
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :take-file-snapshot {:file-id file-id :label label}))
|
||||
(rx/mapcat #(rp/cmd! :create-file-snapshot {:file-id file-id :label label}))
|
||||
(rx/mapcat
|
||||
(fn [{:keys [id]}]
|
||||
(rx/of
|
||||
|
@ -101,7 +101,6 @@
|
|||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :take-file-snapshot {:file-id file-id :created-by "system" :label (dt/format (dt/now) :date-full)}))
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/map #(dw/initialize-file project-id file-id)))
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-version"}))))))
|
||||
|
@ -114,7 +113,7 @@
|
|||
(ptk/reify ::delete-version
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :remove-file-snapshot {:id id})
|
||||
(->> (rp/cmd! :delete-file-snapshot {:id id})
|
||||
(rx/map #(fetch-versions file-id))))))
|
||||
|
||||
(defn pin-version
|
||||
|
|
|
@ -109,8 +109,7 @@
|
|||
(kbd/esc? event)
|
||||
(st/emit! (dwv/update-version-state {:editing nil})))))]
|
||||
|
||||
[:li {:data-testid (dm/str "version(" (:label entry) ")")
|
||||
:class (stl/css :version-entry-wrap)}
|
||||
[:li {:class (stl/css :version-entry-wrap)}
|
||||
[:div {:class (stl/css :version-entry :is-snapshot)}
|
||||
[:img {:class (stl/css :version-entry-avatar)
|
||||
:alt (:fullname profile)
|
||||
|
|
|
@ -12,12 +12,9 @@
|
|||
[app.common.files.validate :as cfv]
|
||||
[app.common.json :as json]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uri :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.changes :as dwc]
|
||||
[app.main.data.dashboard.shortcuts]
|
||||
[app.main.data.preview :as dp]
|
||||
|
@ -33,7 +30,6 @@
|
|||
[app.main.store :as st]
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as timers]
|
||||
[beicon.v2.core :as rx]
|
||||
|
@ -454,66 +450,6 @@
|
|||
[id shape-ref]
|
||||
(st/emit! (dw/set-shape-ref id shape-ref)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SNAPSHOTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn ^:export list-available-snapshots
|
||||
[file-id]
|
||||
(let [file-id (or (d/parse-uuid file-id)
|
||||
(:current-file-id @st/state))]
|
||||
(->> (http/send! {:method :get
|
||||
:uri (u/join cf/public-uri "api/rpc/command/get-file-snapshots")
|
||||
:query {:file-id file-id}})
|
||||
(rx/map http/conditional-decode-transit)
|
||||
(rx/mapcat rp/handle-response)
|
||||
(rx/subs! (fn [result]
|
||||
(let [result (map (fn [row]
|
||||
(update row :id str))
|
||||
result)]
|
||||
(js/console.table (json/->js result))))
|
||||
(fn [cause]
|
||||
(js/console.log "EE:" cause))))
|
||||
nil))
|
||||
|
||||
(defn ^:export take-snapshot
|
||||
[label file-id]
|
||||
(when-let [file-id (or (d/parse-uuid file-id)
|
||||
(:current-file-id @st/state))]
|
||||
(->> (http/send! {:method :post
|
||||
:uri (u/join cf/public-uri "api/rpc/command/take-file-snapshot")
|
||||
:body (http/transit-data {:file-id file-id :label label})})
|
||||
(rx/map http/conditional-decode-transit)
|
||||
(rx/mapcat rp/handle-response)
|
||||
(rx/subs! (fn [{:keys [id]}]
|
||||
(println "Snapshot saved:" (str id) label))
|
||||
(fn [cause]
|
||||
(js/console.log "EE:" cause))))))
|
||||
|
||||
(defn ^:export restore-snapshot
|
||||
[label file-id]
|
||||
(when-let [file-id (or (d/parse-uuid file-id)
|
||||
(:current-file-id @st/state))]
|
||||
(let [snapshot-id (sm/parse-uuid label)
|
||||
label (if snapshot-id nil label)
|
||||
params (cond-> {:file-id file-id}
|
||||
(uuid? snapshot-id)
|
||||
(assoc :id snapshot-id)
|
||||
|
||||
(string? label)
|
||||
(assoc :label label))]
|
||||
(->> (http/send! {:method :post
|
||||
:uri (u/join cf/public-uri "api/rpc/command/restore-file-snapshot")
|
||||
:body (http/transit-data params)})
|
||||
(rx/map http/conditional-decode-transit)
|
||||
(rx/mapcat rp/handle-response)
|
||||
(rx/subs! (fn [_]
|
||||
(println "Snapshot restored " (or snapshot-id label)))
|
||||
#_(.reload js/location)
|
||||
(fn [cause]
|
||||
(js/console.log "EE:" cause)))))))
|
||||
|
||||
|
||||
(defn ^:export enable-text-v2
|
||||
[]
|
||||
(st/emit! (features/enable-feature "text-editor/v2")))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue