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]
|
(fn [system]
|
||||||
(binding [*system* system]
|
(binding [*system* system]
|
||||||
(when (string? label)
|
(when (string? label)
|
||||||
(fsnap/take-file-snapshot! system {:file-id file-id
|
(fsnap/create-file-snapshot! system nil file-id (str "migration/" label)))
|
||||||
:label (str "migration/" label)}))
|
|
||||||
(let [file (get-file system file-id)
|
(let [file (get-file system file-id)
|
||||||
file (process-file! system file :validate? validate?)]
|
file (process-file! system file :validate? validate?)]
|
||||||
|
|
||||||
|
|
|
@ -29,17 +29,13 @@
|
||||||
"SELECT id, label, revn, created_at, created_by, profile_id
|
"SELECT id, label, revn, created_at, created_by, profile_id
|
||||||
FROM file_change
|
FROM file_change
|
||||||
WHERE file_id = ?
|
WHERE file_id = ?
|
||||||
AND created_at < ?
|
|
||||||
AND data IS NOT NULL
|
AND data IS NOT NULL
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT ?")
|
LIMIT 20")
|
||||||
|
|
||||||
(defn get-file-snapshots
|
(defn get-file-snapshots
|
||||||
[{:keys [::db/conn]} {:keys [file-id limit start-at]
|
[conn file-id]
|
||||||
:or {limit Long/MAX_VALUE}}]
|
(db/exec! conn [sql:get-file-snapshots file-id]))
|
||||||
(let [start-at (or start-at (dt/now))
|
|
||||||
limit (min limit 20)]
|
|
||||||
(db/exec! conn [sql:get-file-snapshots file-id start-at limit])))
|
|
||||||
|
|
||||||
(def ^:private schema:get-file-snapshots
|
(def ^:private schema:get-file-snapshots
|
||||||
[:map {:title "get-file-snapshots"}
|
[:map {:title "get-file-snapshots"}
|
||||||
|
@ -48,29 +44,121 @@
|
||||||
(sv/defmethod ::get-file-snapshots
|
(sv/defmethod ::get-file-snapshots
|
||||||
{::doc/added "1.20"
|
{::doc/added "1.20"
|
||||||
::sm/params schema:get-file-snapshots}
|
::sm/params schema:get-file-snapshots}
|
||||||
[cfg params]
|
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
||||||
(db/run! cfg get-file-snapshots 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!
|
(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})
|
(let [storage (sto/resolve cfg {::db/reuse-conn true})
|
||||||
file (files/get-minimal-file conn file-id {::db/for-update true})
|
file (files/get-minimal-file conn file-id {::db/for-update true})
|
||||||
vern (rand-int Integer/MAX_VALUE)
|
vern (rand-int Integer/MAX_VALUE)
|
||||||
snapshot (db/get* conn :file-change
|
snapshot (db/get* conn :file-change
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
:id id}
|
:id snapshot-id}
|
||||||
{::db/for-share true})]
|
{::db/for-share true})]
|
||||||
|
|
||||||
(when-not snapshot
|
(when-not snapshot
|
||||||
(ex/raise :type :not-found
|
(ex/raise :type :not-found
|
||||||
:code :snapshot-not-found
|
:code :snapshot-not-found
|
||||||
:hint "unable to find snapshot with the provided label"
|
:hint "unable to find snapshot with the provided label"
|
||||||
:id id
|
:snapshot-id snapshot-id
|
||||||
:file-id file-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)]
|
(let [snapshot (feat.fdata/resolve-file-data cfg snapshot)]
|
||||||
(when-not (:data snapshot)
|
(when-not (:data snapshot)
|
||||||
(ex/raise :type :precondition
|
(ex/raise :type :validation
|
||||||
:code :snapshot-without-data
|
:code :snapshot-without-data
|
||||||
:hint "snapshot has no data"
|
:hint "snapshot has no data"
|
||||||
:label (:label snapshot)
|
:label (:label snapshot)
|
||||||
|
@ -123,125 +211,77 @@
|
||||||
{:id (:id snapshot)
|
{:id (:id snapshot)
|
||||||
:label (:label snapshot)})))
|
:label (:label snapshot)})))
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private schema:restore-file-snapshot
|
||||||
schema:restore-file-snapshot
|
[:map {:title "restore-file-snapshot"}
|
||||||
[:and
|
[:file-id ::sm/uuid]
|
||||||
[:map
|
[:id ::sm/uuid]])
|
||||||
[:file-id ::sm/uuid]
|
|
||||||
[:id {:optional true} ::sm/uuid]]
|
|
||||||
[::sm/contains-any #{:id :label}]])
|
|
||||||
|
|
||||||
(sv/defmethod ::restore-file-snapshot
|
(sv/defmethod ::restore-file-snapshot
|
||||||
{::doc/added "1.20"
|
{::doc/added "1.20"
|
||||||
::sm/params schema:restore-file-snapshot}
|
::sm/params schema:restore-file-snapshot}
|
||||||
[cfg params]
|
[cfg {:keys [::rpc/profile-id file-id id] :as params}]
|
||||||
(db/tx-run! cfg restore-file-snapshot! params))
|
(db/tx-run! cfg
|
||||||
|
(fn [{:keys [::db/conn] :as cfg}]
|
||||||
(defn- get-file
|
(files/check-edition-permissions! conn profile-id file-id)
|
||||||
[cfg file-id]
|
(create-file-snapshot! cfg profile-id file-id :system)
|
||||||
(let [file (->> (db/get cfg :file {:id file-id})
|
(restore-file-snapshot! cfg file-id 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))
|
|
||||||
|
|
||||||
(def ^:private schema:update-file-snapshot
|
(def ^:private schema:update-file-snapshot
|
||||||
[:map {:title "update-file-snapshot"}
|
[:map {:title "update-file-snapshot"}
|
||||||
[:id ::sm/uuid]
|
[:id ::sm/uuid]
|
||||||
[:label ::sm/text]])
|
[:label ::sm/text]])
|
||||||
|
|
||||||
(defn update-file-snapshot!
|
(defn- update-file-snapshot!
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [id label]}]
|
[conn snapshot-id label]
|
||||||
(let [result
|
(-> (db/update! conn :file-change
|
||||||
(db/update! conn :file-change
|
{:label label
|
||||||
{:label label
|
:created-by "user"}
|
||||||
:created-by "user"}
|
{:id snapshot-id}
|
||||||
{:id id}
|
{::db/return-keys true})
|
||||||
{::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
|
(sv/defmethod ::update-file-snapshot
|
||||||
{::doc/added "1.20"
|
{::doc/added "1.20"
|
||||||
::sm/params schema:update-file-snapshot}
|
::sm/params schema:update-file-snapshot}
|
||||||
[cfg params]
|
[cfg {:keys [::rpc/profile-id id label]}]
|
||||||
(db/tx-run! cfg update-file-snapshot! params))
|
(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
|
(def ^:private schema:remove-file-snapshot
|
||||||
[:map {:title "remove-file-snapshot"}
|
[:map {:title "remove-file-snapshot"}
|
||||||
[:id ::sm/uuid]])
|
[:id ::sm/uuid]])
|
||||||
|
|
||||||
(defn remove-file-snapshot!
|
(defn- delete-file-snapshot!
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [id]}]
|
[conn snapshot-id]
|
||||||
(db/delete! conn :file-change
|
(db/delete! conn :file-change
|
||||||
{:id id :created-by "user"}
|
{:id snapshot-id}
|
||||||
{::db/return-keys false})
|
{::db/return-keys false})
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(sv/defmethod ::remove-file-snapshot
|
(sv/defmethod ::delete-file-snapshot
|
||||||
{::doc/added "1.20"
|
{::doc/added "1.20"
|
||||||
::sm/params schema:remove-file-snapshot}
|
::sm/params schema:remove-file-snapshot}
|
||||||
[cfg params]
|
[cfg {:keys [::rpc/profile-id id]}]
|
||||||
(db/tx-run! cfg remove-file-snapshot! params))
|
(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(?)
|
WHERE file_id = ANY(?)
|
||||||
AND id IS NOT NULL")
|
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
|
"Get a seq parirs of file-id and snapshot-id for a set of files
|
||||||
and specified label"
|
and specified label"
|
||||||
[conn label ids]
|
[conn file-ids label]
|
||||||
(db/exec! conn [sql:snapshots-with-file 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!
|
(defn take-team-snapshot!
|
||||||
[system team-id label]
|
[system team-id label]
|
||||||
(let [conn (db/get-connection system)]
|
(let [conn (db/get-connection system)]
|
||||||
(->> (feat.comp-v2/get-and-lock-team-files conn team-id)
|
(->> (feat.comp-v2/get-and-lock-team-files conn team-id)
|
||||||
(map (fn [file-id]
|
(reduce (fn [result file-id]
|
||||||
{:file-id file-id
|
(fsnap/create-file-snapshot! system nil file-id label)
|
||||||
:label label}))
|
|
||||||
(reduce (fn [result params]
|
|
||||||
(fsnap/take-file-snapshot! conn params)
|
|
||||||
(inc result))
|
(inc result))
|
||||||
0))))
|
0))))
|
||||||
|
|
||||||
|
@ -147,7 +144,7 @@
|
||||||
ids (->> (feat.comp-v2/get-and-lock-team-files conn team-id)
|
ids (->> (feat.comp-v2/get-and-lock-team-files conn team-id)
|
||||||
(into #{}))
|
(into #{}))
|
||||||
|
|
||||||
snap (get-file-snapshots conn label ids)
|
snap (search-file-snapshots conn ids label)
|
||||||
|
|
||||||
ids' (into #{} (map :file-id) snap)
|
ids' (into #{} (map :file-id) snap)
|
||||||
team (-> (feat.comp-v2/get-team conn team-id)
|
team (-> (feat.comp-v2/get-team conn team-id)
|
||||||
|
@ -157,8 +154,8 @@
|
||||||
(throw (RuntimeException. "no uniform snapshot available")))
|
(throw (RuntimeException. "no uniform snapshot available")))
|
||||||
|
|
||||||
(feat.comp-v2/update-team! conn team)
|
(feat.comp-v2/update-team! conn team)
|
||||||
(reduce (fn [result params]
|
(reduce (fn [result {:keys [file-id id]}]
|
||||||
(fsnap/restore-file-snapshot! conn params)
|
(fsnap/restore-file-snapshot! system file-id id)
|
||||||
(inc result))
|
(inc result))
|
||||||
0
|
0
|
||||||
snap)))
|
snap)))
|
||||||
|
@ -167,7 +164,7 @@
|
||||||
[system file-id update-fn & {:keys [label validate? with-libraries?] :or {validate? true} :as opts}]
|
[system file-id update-fn & {:keys [label validate? with-libraries?] :or {validate? true} :as opts}]
|
||||||
|
|
||||||
(when (string? label)
|
(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)
|
(let [conn (db/get-connection system)
|
||||||
file (get-file system file-id opts)
|
file (get-file system file-id opts)
|
||||||
|
|
|
@ -311,28 +311,25 @@
|
||||||
collectable file-changes entry."
|
collectable file-changes entry."
|
||||||
[& {:keys [file-id label]}]
|
[& {:keys [file-id label]}]
|
||||||
(let [file-id (h/parse-uuid file-id)]
|
(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!
|
(defn restore-file-snapshot!
|
||||||
[file-id label]
|
[file-id label]
|
||||||
(let [file-id (h/parse-uuid file-id)]
|
(let [file-id (h/parse-uuid file-id)]
|
||||||
(db/tx-run! main/system
|
(db/tx-run! main/system
|
||||||
(fn [{:keys [::db/conn] :as 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)
|
(map :id)
|
||||||
(first))]
|
(first))]
|
||||||
(fsnap/restore-file-snapshot! system
|
(fsnap/restore-file-snapshot! system file-id (:id snapshot)))))))
|
||||||
{:id (:id snapshot)
|
|
||||||
:file-id file-id}))))))
|
|
||||||
|
|
||||||
(defn list-file-snapshots!
|
(defn list-file-snapshots!
|
||||||
[file-id & {:keys [limit]}]
|
[file-id & {:as _}]
|
||||||
(let [file-id (h/parse-uuid file-id)]
|
(let [file-id (h/parse-uuid file-id)]
|
||||||
(db/tx-run! main/system
|
(db/tx-run! main/system
|
||||||
(fn [system]
|
(fn [{:keys [::db/conn]}]
|
||||||
(let [params {:file-id file-id :limit limit}]
|
(->> (fsnap/get-file-snapshots conn file-id)
|
||||||
(->> (fsnap/get-file-snapshots system (d/without-nils params))
|
(print-table [:label :id :revn :created-at]))))))
|
||||||
(print-table [:label :id :revn :created-at])))))))
|
|
||||||
|
|
||||||
(defn take-team-snapshot!
|
(defn take-team-snapshot!
|
||||||
[team-id & {:keys [label rollback?] :or {rollback? true}}]
|
[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 page.getByLabel("History (Alt+H)").click();
|
||||||
|
|
||||||
await workspacePage.mockRPC(
|
await workspacePage.mockRPC(
|
||||||
"take-file-snapshot",
|
"create-file-snapshot",
|
||||||
"workspace/versions-take-snapshot-1.json",
|
"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").fill("INIT");
|
||||||
await page.getByRole("textbox").press("Enter");
|
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 page.getByRole("button", { name: "Restore" }).click();
|
||||||
|
|
||||||
await workspacePage.mockRPC(
|
await workspacePage.mockRPC(
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||||
(rx/filter #(or (nil? %) (= :saved %)))
|
(rx/filter #(or (nil? %) (= :saved %)))
|
||||||
(rx/take 1)
|
(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
|
(rx/mapcat
|
||||||
(fn [{:keys [id]}]
|
(fn [{:keys [id]}]
|
||||||
(rx/of
|
(rx/of
|
||||||
|
@ -101,7 +101,6 @@
|
||||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||||
(rx/filter #(or (nil? %) (= :saved %)))
|
(rx/filter #(or (nil? %) (= :saved %)))
|
||||||
(rx/take 1)
|
(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/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||||
(rx/map #(dw/initialize-file project-id file-id)))
|
(rx/map #(dw/initialize-file project-id file-id)))
|
||||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-version"}))))))
|
(rx/of (ptk/event ::ev/event {::ev/name "restore-version"}))))))
|
||||||
|
@ -114,7 +113,7 @@
|
||||||
(ptk/reify ::delete-version
|
(ptk/reify ::delete-version
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (rp/cmd! :remove-file-snapshot {:id id})
|
(->> (rp/cmd! :delete-file-snapshot {:id id})
|
||||||
(rx/map #(fetch-versions file-id))))))
|
(rx/map #(fetch-versions file-id))))))
|
||||||
|
|
||||||
(defn pin-version
|
(defn pin-version
|
||||||
|
|
|
@ -109,8 +109,7 @@
|
||||||
(kbd/esc? event)
|
(kbd/esc? event)
|
||||||
(st/emit! (dwv/update-version-state {:editing nil})))))]
|
(st/emit! (dwv/update-version-state {:editing nil})))))]
|
||||||
|
|
||||||
[:li {:data-testid (dm/str "version(" (:label entry) ")")
|
[:li {:class (stl/css :version-entry-wrap)}
|
||||||
:class (stl/css :version-entry-wrap)}
|
|
||||||
[:div {:class (stl/css :version-entry :is-snapshot)}
|
[:div {:class (stl/css :version-entry :is-snapshot)}
|
||||||
[:img {:class (stl/css :version-entry-avatar)
|
[:img {:class (stl/css :version-entry-avatar)
|
||||||
:alt (:fullname profile)
|
:alt (:fullname profile)
|
||||||
|
|
|
@ -12,12 +12,9 @@
|
||||||
[app.common.files.validate :as cfv]
|
[app.common.files.validate :as cfv]
|
||||||
[app.common.json :as json]
|
[app.common.json :as json]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.common.schema :as sm]
|
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.common.types.file :as ctf]
|
[app.common.types.file :as ctf]
|
||||||
[app.common.uri :as u]
|
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
|
||||||
[app.main.data.changes :as dwc]
|
[app.main.data.changes :as dwc]
|
||||||
[app.main.data.dashboard.shortcuts]
|
[app.main.data.dashboard.shortcuts]
|
||||||
[app.main.data.preview :as dp]
|
[app.main.data.preview :as dp]
|
||||||
|
@ -33,7 +30,6 @@
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.debug :as dbg]
|
[app.util.debug :as dbg]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.http :as http]
|
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.timers :as timers]
|
[app.util.timers :as timers]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
|
@ -454,66 +450,6 @@
|
||||||
[id shape-ref]
|
[id shape-ref]
|
||||||
(st/emit! (dw/set-shape-ref 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
|
(defn ^:export enable-text-v2
|
||||||
[]
|
[]
|
||||||
(st/emit! (features/enable-feature "text-editor/v2")))
|
(st/emit! (features/enable-feature "text-editor/v2")))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue