0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-25 07:58:49 -05:00

🎉 Add rpc method for working with file snapshots

This commit is contained in:
Andrey Antukh 2023-08-03 17:46:17 +02:00
parent d1128a6b1e
commit 13d68a53c0
3 changed files with 155 additions and 84 deletions

View file

@ -214,6 +214,7 @@
'app.rpc.commands.files-share
'app.rpc.commands.files-temp
'app.rpc.commands.files-update
'app.rpc.commands.files-snapshot
'app.rpc.commands.files-thumbnails
'app.rpc.commands.ldap
'app.rpc.commands.management

View file

@ -0,0 +1,136 @@
;; 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 app.rpc.commands.files-snapshot
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.main :as-alias main]
[app.media :as media]
[app.rpc :as-alias rpc]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as-alias doc]
[app.storage :as sto]
[app.util.services :as sv]
[app.util.time :as dt]))
(defn check-authorized!
[{:keys [::db/pool]} profile-id]
(when-not (or (= "devenv" (cf/get :host))
(let [profile (ex/ignoring (profile/get-profile pool profile-id))
admins (or (cf/get :admins) #{})]
(contains? admins (:email profile))))
(ex/raise :type :authentication
:code :authentication-required
:hint "only admins allowed")))
(defn get-file-snapshots
[{:keys [::db/conn]} {:keys [file-id limit start-at]
:or {limit Long/MAX_VALUE}}]
(let [query (str "select id, label, revn, created_at "
" from file_change "
" where file_id = ? "
" and created_at < ? "
" and label is not null "
" and data is not null "
" order by created_at desc "
" limit ?")
start-at (or start-at (dt/now))
limit (min limit 20)]
(->> (db/exec! conn [query file-id start-at limit])
(mapv (fn [row]
(update row :created-at dt/format-instant :rfc1123))))))
(def ^:private schema:get-file-snapshots
[:map [:file-id ::sm/uuid]])
(sv/defmethod ::get-file-snapshots
{::doc/added "1.20"
::doc/skip true
::sm/params schema:get-file-snapshots}
[cfg {:keys [::rpc/profile-id] :as params}]
(check-authorized! cfg profile-id)
(db/run! cfg #(get-file-snapshots % params)))
(defn restore-file-snapshot!
[{:keys [::db/conn ::sto/storage] :as cfg} {:keys [file-id id]}]
(let [storage (media/configure-assets-storage storage conn)
params {:id id :file-id file-id}
options {:columns [:id :data :revn]}
snapshot (db/get* conn :file-change params options)]
(when (and (some? snapshot)
(some? (:data snapshot)))
(l/debug :hint "snapshot found"
:snapshot-id (:id snapshot)
:file-id file-id)
(db/update! conn :file
{:data (:data snapshot)}
{:id file-id})
;; clean object thumbnails
(let [sql (str "delete from file_object_thumbnail "
" where file_id=? returning media_id")
res (db/exec! conn [sql file-id])]
(doseq [media-id (into #{} (keep :media-id) res)]
(sto/del-object! storage media-id)))
;; clean object thumbnails
(let [sql (str "delete from file_thumbnail "
" where file_id=? returning media_id")
res (db/exec! conn [sql file-id])]
(doseq [media-id (into #{} (keep :media-id) res)]
(sto/del-object! storage media-id)))
{:id (:id snapshot)})))
(def ^:private schema:restore-file-snapshot
[:map
[:file-id ::sm/uuid]
[:id ::sm/uuid]])
(sv/defmethod ::restore-file-snapshot
{::doc/added "1.20"
::doc/skip true
::sm/params schema:restore-file-snapshot}
[cfg {:keys [::rpc/profile-id] :as params}]
(check-authorized! cfg profile-id)
(db/tx-run! cfg #(restore-file-snapshot! % params)))
(defn take-file-snapshot!
[{:keys [::db/conn]} {:keys [file-id label]}]
(when-let [file (db/get* conn :file {:id file-id})]
(let [id (uuid/next)
label (or label (str "Snapshot at " (dt/format-instant (dt/now) :rfc1123)))]
(l/debug :hint "persisting file snapshot" :file-id file-id :label label)
(db/insert! conn :file-change
{:id id
:revn (:revn file)
:data (:data file)
:features (:features file)
:file-id (:id file)
:label label})
{:id id})))
(def ^:private schema:take-file-snapshot
[:map [:file-id ::sm/uuid]])
(sv/defmethod ::take-file-snapshot
{::doc/added "1.20"
::doc/skip true
::sm/params schema:take-file-snapshot}
[cfg {:keys [::rpc/profile-id] :as params}]
(check-authorized! cfg profile-id)
(db/tx-run! cfg #(take-file-snapshot! % params)))

View file

@ -17,6 +17,7 @@
[app.media :as media]
[app.rpc.commands.auth :as auth]
[app.rpc.commands.profile :as profile]
[app.rpc.commands.files-snapshot :as fsnap]
[app.srepl.fixes :as f]
[app.srepl.helpers :as h]
[app.storage :as sto]
@ -105,7 +106,6 @@
(db/delete! conn :http-session {:profile-id (:id profile)})
:blocked))))
(defn enable-objects-map-feature-on-file!
[system & {:keys [save? id]}]
(letfn [(update-file [{:keys [features] :as file}]
@ -172,94 +172,28 @@
"An internal helper that persist the file snapshot using non-gc
collectable file-changes entry."
[system & {:keys [file-id label]}]
(let [label (or label (str "Snapshot at " (dt/format-instant (dt/now) :rfc1123)))
file-id (h/parse-uuid file-id)
id (uuid/next)]
(let [file-id (h/parse-uuid file-id)]
(db/tx-run! system
(fn [{:keys [::db/conn]}]
(when-let [file (db/get* conn :file {:id file-id})]
(h/println! "=> persisting snapshot for" file-id)
(db/insert! conn :file-change
{:id id
:revn (:revn file)
:data (:data file)
:features (:features file)
:file-id (:id file)
:label label})
id)))))
(fn [cfg]
(fsnap/take-file-snapshot! cfg {:file-id file-id :label label})))))
(defn restore-file-snapshot!
[system & {:keys [file-id id label]}]
(letfn [(restore-snapshot! [{:keys [::db/conn ::sto/storage]} file-id snapshot]
(when (and (some? snapshot)
(some? (:data snapshot)))
[system & {:keys [file-id id]}]
(db/tx-run! system
(fn [cfg]
(let [file-id (h/parse-uuid file-id)
id (h/parse-uuid id)]
(h/println! "-> snapshot found:" (:id snapshot))
(h/println! "-> restoring it on file:" file-id)
(db/update! conn :file
{:data (:data snapshot)}
{:id file-id})
(if (and (uuid? id) (uuid? file-id))
(fsnap/restore-file-snapshot! cfg {:id id :file-id file-id})
(println "=> invalid parameters"))))))
;; clean object thumbnails
(let [sql (str "delete from file_object_thumbnail "
" where file_id=? returning media_id")
res (db/exec! conn [sql file-id])]
(doseq [media-id (into #{} (keep :media-id) res)]
(sto/del-object! storage media-id)))))
(execute [{:keys [::db/conn] :as cfg}]
(let [file-id (h/parse-uuid file-id)
id (h/parse-uuid id)
cfg (update cfg ::sto/storage media/configure-assets-storage conn)]
(cond
(and (uuid? id) (uuid? file-id))
(let [params {:id id :file-id file-id}
options {:columns [:id :data :revn]}
snapshot (db/get* conn :file-change params options)]
(restore-snapshot! cfg file-id snapshot))
(uuid? file-id)
(let [params (cond-> {:file-id file-id}
(string? label)
(assoc :label label))
options {:columns [:id :data :revn]}
snapshot (db/get* conn :file-change params options)]
(restore-snapshot! cfg file-id snapshot))
:else
(println "=> invalid parameters"))))]
(db/tx-run! system execute)))
(defn list-file-snapshots!
[system & {:keys [file-id limit chunk-size start-at]
:or {chunk-size 10 limit Long/MAX_VALUE}}]
(letfn [(get-chunk [ds cursor]
(let [query (str "select id, label, revn, created_at "
" from file_change "
" where file_id = ? "
" and created_at < ? "
" and label is not null "
" and data is not null "
" order by created_at desc "
" limit ?")
file-id (if (string? file-id)
(d/parse-uuid file-id)
file-id)
rows (db/exec! ds [query file-id cursor chunk-size])]
[(some->> rows peek :created-at) (seq rows)]))
(get-candidates [ds]
(->> (d/iteration (partial get-chunk ds)
:vf second
:kf first
:initk (or start-at (dt/now)))
(take limit)))]
(db/tx-run! system (fn [system]
(->> (fsnap/get-file-snapshots
(map (fn [row]
(update row :created-at dt/format-instant :rfc1123)))
[system & {:keys [file-id limit]}]
(db/tx-run! system (fn [system]
(let [params {:file-id (h/parse-uuid file-id)
:limit limit}]
(->> (fsnap/get-file-snapshots system (d/without-nils params))
(print-table [:id :revn :created-at :label]))))))