mirror of
https://github.com/penpot/penpot.git
synced 2025-08-06 04:18:19 +02:00
✨ Add several improvements to admin pannel
This commit is contained in:
parent
ea0044f69a
commit
fa72bb4adf
8 changed files with 233 additions and 252 deletions
|
@ -17,38 +17,6 @@ Debug Main Page
|
||||||
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
|
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Download file data:</legend>
|
|
||||||
<desc>Given an FILE-ID, downloads the file data as file. The file data is encoded using transit.</desc>
|
|
||||||
<form method="get" action="/dbg/file/data">
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit" name="download" value="Download" />
|
|
||||||
<input type="submit" name="clone" value="Clone" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Upload File Data:</legend>
|
|
||||||
<desc>Create a new file on your draft projects using the file downloaded from the previous section.</desc>
|
|
||||||
<form method="post" enctype="multipart/form-data" action="/dbg/file/data">
|
|
||||||
<div class="row">
|
|
||||||
<input type="file" name="file" value="" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<label>Import with same id?</label>
|
|
||||||
<input type="checkbox" name="reuseid" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit" value="Upload" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Profile Management</legend>
|
<legend>Profile Management</legend>
|
||||||
<form method="post" action="/dbg/actions/resend-email-verification">
|
<form method="post" action="/dbg/actions/resend-email-verification">
|
||||||
|
@ -81,6 +49,50 @@ Debug Main Page
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="widget">
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Download RAW file data:</legend>
|
||||||
|
<desc>Given an FILE-ID, downloads the file AS-IS (no validation
|
||||||
|
checks, just exports the file data and related objects in raw)
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<b>WARNING: this operation does not performs any checks</b>
|
||||||
|
</desc>
|
||||||
|
<form method="get" action="/dbg/actions/file-raw-export-import">
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input type="submit" name="download" value="Download" />
|
||||||
|
<input type="submit" name="clone" value="Clone" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Upload File Data:</legend>
|
||||||
|
<desc>Create a new file on your draft projects using the file downloaded from the previous section.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<b>WARNING: this operation does not performs any checks</b>
|
||||||
|
</desc>
|
||||||
|
<form method="post" enctype="multipart/form-data" action="/dbg/actions/file-raw-export-import">
|
||||||
|
<div class="row">
|
||||||
|
<input type="file" name="file" value="" />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>Import with same id?</label>
|
||||||
|
<input type="checkbox" name="reuseid" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<input type="submit" value="Upload" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
<section class="widget">
|
<section class="widget">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Export binfile:</legend>
|
<legend>Export binfile:</legend>
|
||||||
|
@ -88,7 +100,7 @@ Debug Main Page
|
||||||
the related libraries in a single custom formatted binary
|
the related libraries in a single custom formatted binary
|
||||||
file.</desc>
|
file.</desc>
|
||||||
|
|
||||||
<form method="get" action="/dbg/file/export">
|
<form method="get" action="/dbg/actions/file-export">
|
||||||
<div class="row set-of-inputs">
|
<div class="row set-of-inputs">
|
||||||
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
||||||
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
||||||
|
@ -116,7 +128,7 @@ Debug Main Page
|
||||||
<legend>Import binfile:</legend>
|
<legend>Import binfile:</legend>
|
||||||
<desc>Import penpot file in binary format.</desc>
|
<desc>Import penpot file in binary format.</desc>
|
||||||
|
|
||||||
<form method="post" enctype="multipart/form-data" action="/dbg/file/import">
|
<form method="post" enctype="multipart/form-data" action="/dbg/actions/file-import">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="file" name="file" value="" />
|
<input type="file" name="file" value="" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -130,79 +142,27 @@ Debug Main Page
|
||||||
|
|
||||||
<section class="widget">
|
<section class="widget">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Reset file version</legend>
|
<legend>Feature Flags for Team</legend>
|
||||||
<desc>Allows reset file data version to a specific number/</desc>
|
|
||||||
|
|
||||||
<form method="post" action="/dbg/actions/reset-file-version">
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="number" style="width:100px" name="version" placeholder="version" value="32" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<label for="force-version">Are you sure?</label>
|
|
||||||
<input id="force-version" type="checkbox" name="force" />
|
|
||||||
<br />
|
|
||||||
<small>
|
|
||||||
This is a just a security double check for prevent non intentional submits.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit" value="Submit" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="widget">
|
|
||||||
<h2>Feature Flags</h2>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Enable</legend>
|
|
||||||
<desc>Add a feature flag to a team</desc>
|
<desc>Add a feature flag to a team</desc>
|
||||||
<form method="post" action="/dbg/actions/add-team-feature">
|
<form method="post" action="/dbg/actions/handle-team-features">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
|
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="text" style="width:100px" name="feature" placeholder="feature" value="" />
|
<select type="text" style="width:100px" name="feature">
|
||||||
|
{% for feature in supported-features %}
|
||||||
|
<option value="{{feature}}">{{feature}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="check-feature">Skip feature check</label>
|
<select style="width:100px" name="action">
|
||||||
<input id="check-feature" type="checkbox" name="skip-check" />
|
<option value="">Action...</option>
|
||||||
<br />
|
<option value="show">Show</option>
|
||||||
<small>
|
<option value="enable">Enable</option>
|
||||||
Do not check if the feature is supported
|
<option value="disable">Disable</option>
|
||||||
</small>
|
</select>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<label for="force-version">Are you sure?</label>
|
|
||||||
<input id="force-version" type="checkbox" name="force" />
|
|
||||||
<br />
|
|
||||||
<small>
|
|
||||||
This is a just a security double check for prevent non intentional submits.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit" value="Submit" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Disable</legend>
|
|
||||||
<desc>Remove a feature flag from a team</desc>
|
|
||||||
<form method="post" action="/dbg/actions/remove-team-feature">
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" style="width:100px" name="feature" placeholder="feature" value="" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -7,7 +7,9 @@ penpot - error list
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<nav>
|
<nav>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h1>Error reports (last 200)</h1>
|
<h1>Error reports (last 200)
|
||||||
|
<a href="/dbg">[GO BACK]</a>
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<main class="horizontal-list">
|
<main class="horizontal-list">
|
||||||
|
|
|
@ -155,7 +155,7 @@
|
||||||
(defn decode-file
|
(defn decode-file
|
||||||
"A general purpose file decoding function that resolves all external
|
"A general purpose file decoding function that resolves all external
|
||||||
pointers, run migrations and return plain vanilla file map"
|
pointers, run migrations and return plain vanilla file map"
|
||||||
[cfg {:keys [id] :as file}]
|
[cfg {:keys [id] :as file} & {:keys [migrate?] :or {migrate? true}}]
|
||||||
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
|
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
|
||||||
(let [file (->> file
|
(let [file (->> file
|
||||||
(feat.fmigr/resolve-applied-migrations cfg)
|
(feat.fmigr/resolve-applied-migrations cfg)
|
||||||
|
@ -168,7 +168,7 @@
|
||||||
(update :data feat.fdata/process-pointers deref)
|
(update :data feat.fdata/process-pointers deref)
|
||||||
(update :data feat.fdata/process-objects (partial into {}))
|
(update :data feat.fdata/process-objects (partial into {}))
|
||||||
(update :data assoc :id id)
|
(update :data assoc :id id)
|
||||||
(fmg/migrate-file libs)))))
|
(cond-> migrate? (fmg/migrate-file libs))))))
|
||||||
|
|
||||||
(defn get-file
|
(defn get-file
|
||||||
"Get file, resolve all features and apply migrations.
|
"Get file, resolve all features and apply migrations.
|
||||||
|
|
|
@ -37,3 +37,9 @@
|
||||||
{::db/return-keys false
|
{::db/return-keys false
|
||||||
::sql/on-conflict-do-nothing true})
|
::sql/on-conflict-do-nothing true})
|
||||||
(db/get-update-count))))
|
(db/get-update-count))))
|
||||||
|
|
||||||
|
(defn reset-migrations!
|
||||||
|
"Replace file migrations"
|
||||||
|
[conn {:keys [id] :as file}]
|
||||||
|
(db/delete! conn :file-migration {:file-id id})
|
||||||
|
(upsert-migrations! conn file))
|
||||||
|
|
|
@ -15,9 +15,11 @@
|
||||||
[app.common.features :as cfeat]
|
[app.common.features :as cfeat]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.common.pprint :as pp]
|
[app.common.pprint :as pp]
|
||||||
|
[app.common.transit :as t]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
|
[app.features.file-migrations :as feat.fmig]
|
||||||
[app.http.session :as session]
|
[app.http.session :as session]
|
||||||
[app.rpc.commands.auth :as auth]
|
[app.rpc.commands.auth :as auth]
|
||||||
[app.rpc.commands.files-create :refer [create-file]]
|
[app.rpc.commands.files-create :refer [create-file]]
|
||||||
|
@ -50,26 +52,26 @@
|
||||||
{::yres/status 200
|
{::yres/status 200
|
||||||
::yres/headers {"content-type" "text/html"}
|
::yres/headers {"content-type" "text/html"}
|
||||||
::yres/body (-> (io/resource "app/templates/debug.tmpl")
|
::yres/body (-> (io/resource "app/templates/debug.tmpl")
|
||||||
(tmpl/render {:version (:full cf/version)}))})
|
(tmpl/render {:version (:full cf/version)
|
||||||
|
:supported-features cfeat/supported-features}))})
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; FILE CHANGES
|
;; FILE CHANGES
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defn prepare-response
|
(defn- get-resolved-file
|
||||||
[body]
|
[cfg file-id]
|
||||||
(let [headers {"content-type" "application/transit+json"}]
|
(some-> (bfc/get-file cfg file-id :migrate? false)
|
||||||
{::yres/status 200
|
(update :data blob/encode)))
|
||||||
::yres/body body
|
|
||||||
::yres/headers headers}))
|
|
||||||
|
|
||||||
(defn prepare-download-response
|
(defn prepare-download
|
||||||
[body filename]
|
[file filename]
|
||||||
(let [headers {"content-disposition" (str "attachment; filename=" filename)
|
|
||||||
"content-type" "application/octet-stream"}]
|
|
||||||
{::yres/status 200
|
{::yres/status 200
|
||||||
::yres/body body
|
::yres/headers
|
||||||
::yres/headers headers}))
|
{"content-disposition" (str "attachment; filename=" filename ".json")
|
||||||
|
"content-type" "application/octet-stream"}
|
||||||
|
::yres/body
|
||||||
|
(t/encode file {:type :json-verbose})})
|
||||||
|
|
||||||
(def sql:retrieve-range-of-changes
|
(def sql:retrieve-range-of-changes
|
||||||
"select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn")
|
"select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn")
|
||||||
|
@ -77,45 +79,51 @@
|
||||||
(def sql:retrieve-single-change
|
(def sql:retrieve-single-change
|
||||||
"select revn, changes, data from file_change where file_id=? and revn = ?")
|
"select revn, changes, data from file_change where file_id=? and revn = ?")
|
||||||
|
|
||||||
(defn- retrieve-file-data
|
(defn- download-file-data
|
||||||
[{:keys [::db/pool]} {:keys [params ::session/profile-id] :as request}]
|
[cfg {:keys [params ::session/profile-id] :as request}]
|
||||||
(let [file-id (some-> params :file-id parse-uuid)
|
(let [file-id (some-> params :file-id parse-uuid)
|
||||||
revn (some-> params :revn parse-long)
|
|
||||||
filename (str file-id)]
|
filename (str file-id)]
|
||||||
|
|
||||||
(when-not file-id
|
(when-not file-id
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :missing-arguments))
|
:code :missing-arguments))
|
||||||
|
|
||||||
(let [data (if (integer? revn)
|
(if-let [file (get-resolved-file cfg file-id)]
|
||||||
(some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data)
|
|
||||||
(some-> (db/get-by-id pool :file file-id) :data))]
|
|
||||||
|
|
||||||
(when-not data
|
|
||||||
(ex/raise :type :not-found
|
|
||||||
:code :enpty-data
|
|
||||||
:hint "empty response"))
|
|
||||||
(cond
|
(cond
|
||||||
(contains? params :download)
|
(contains? params :download)
|
||||||
(prepare-download-response data filename)
|
(prepare-download file filename)
|
||||||
|
|
||||||
(contains? params :clone)
|
(contains? params :clone)
|
||||||
(let [profile (profile/get-profile pool profile-id)
|
(db/tx-run! cfg
|
||||||
project-id (:default-project-id profile)]
|
(fn [{:keys [::db/conn] :as cfg}]
|
||||||
|
(let [profile (profile/get-profile conn profile-id)
|
||||||
(db/run! pool (fn [{:keys [::db/conn] :as cfg}]
|
project-id (:default-project-id profile)
|
||||||
(create-file cfg {:id file-id
|
file (-> (create-file cfg {:id (uuid/next)
|
||||||
:name (str "Cloned file: " filename)
|
:name (str "Cloned: " (:name file))
|
||||||
|
:features (:features file)
|
||||||
:project-id project-id
|
:project-id project-id
|
||||||
:profile-id profile-id})
|
:profile-id profile-id})
|
||||||
|
(assoc :data (:data file))
|
||||||
|
(assoc :migrations (:migrations file)))]
|
||||||
|
|
||||||
|
(feat.fmig/reset-migrations! conn file)
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:data data}
|
{:data (:data file)}
|
||||||
{:id file-id})
|
{:id (:id file)}
|
||||||
|
{::db/return-keys false})
|
||||||
|
|
||||||
|
|
||||||
{::yres/status 201
|
{::yres/status 201
|
||||||
::yres/body "OK CREATED"})))
|
::yres/body "OK CLONED"})))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(prepare-response (blob/decode data))))))
|
(ex/raise :type :validation
|
||||||
|
:code :invalid-params
|
||||||
|
:hint "invalid button"))
|
||||||
|
|
||||||
|
(ex/raise :type :not-found
|
||||||
|
:code :enpty-data
|
||||||
|
:hint "empty response"))))
|
||||||
|
|
||||||
(defn- is-file-exists?
|
(defn- is-file-exists?
|
||||||
[pool id]
|
[pool id]
|
||||||
|
@ -123,81 +131,61 @@
|
||||||
(-> (db/exec-one! pool [sql id]) :exists)))
|
(-> (db/exec-one! pool [sql id]) :exists)))
|
||||||
|
|
||||||
(defn- upload-file-data
|
(defn- upload-file-data
|
||||||
[{:keys [::db/pool]} {:keys [::session/profile-id params] :as request}]
|
[{:keys [::db/pool] :as cfg} {:keys [::session/profile-id params] :as request}]
|
||||||
(let [profile (profile/get-profile pool profile-id)
|
(let [profile (profile/get-profile pool profile-id)
|
||||||
project-id (:default-project-id profile)
|
project-id (:default-project-id profile)
|
||||||
data (some-> params :file :path io/read*)]
|
file (some-> params :file :path io/read* t/decode)]
|
||||||
|
|
||||||
(if (and data project-id)
|
(if (and file project-id)
|
||||||
(let [fname (str "Imported file *: " (dt/now))
|
(let [fname (str "Imported: " (:name file) "(" (dt/now) ")")
|
||||||
reuse-id? (contains? params :reuseid)
|
reuse-id? (contains? params :reuseid)
|
||||||
file-id (or (and reuse-id? (ex/ignoring (-> params :file :filename parse-uuid)))
|
file-id (or (and reuse-id? (ex/ignoring (-> params :file :filename parse-uuid)))
|
||||||
(uuid/next))]
|
(uuid/next))]
|
||||||
|
|
||||||
(if (and reuse-id? file-id
|
(if (and reuse-id? file-id
|
||||||
(is-file-exists? pool file-id))
|
(is-file-exists? pool file-id))
|
||||||
(do
|
(db/tx-run! cfg
|
||||||
(db/update! pool :file
|
(fn [{:keys [::db/conn] :as cfg}]
|
||||||
{:data data
|
(db/update! conn :file
|
||||||
|
{:data (:data file)
|
||||||
|
:features (into-array (:features file))
|
||||||
:deleted-at nil}
|
:deleted-at nil}
|
||||||
{:id file-id})
|
{:id file-id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
(feat.fmig/reset-migrations! conn file)
|
||||||
{::yres/status 200
|
{::yres/status 200
|
||||||
::yres/body "OK UPDATED"})
|
::yres/body "OK UPDATED"}))
|
||||||
|
|
||||||
(db/run! pool (fn [{:keys [::db/conn] :as cfg}]
|
(db/tx-run! cfg
|
||||||
(create-file cfg {:id file-id
|
(fn [{:keys [::db/conn] :as cfg}]
|
||||||
|
(let [file (-> (create-file cfg {:id file-id
|
||||||
:name fname
|
:name fname
|
||||||
|
:features (:features file)
|
||||||
:project-id project-id
|
:project-id project-id
|
||||||
:profile-id profile-id})
|
:profile-id profile-id})
|
||||||
|
(assoc :data (:data file))
|
||||||
|
(assoc :migrations (:migrations file)))]
|
||||||
|
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:data data}
|
{:data (:data file)}
|
||||||
{:id file-id})
|
{:id file-id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
(feat.fmig/reset-migrations! conn file)
|
||||||
{::yres/status 201
|
{::yres/status 201
|
||||||
::yres/body "OK CREATED"}))))
|
::yres/body "OK CREATED"})))))
|
||||||
|
|
||||||
{::yres/status 500
|
(ex/raise :type :validation
|
||||||
::yres/body "ERROR"})))
|
:code :invalid-params
|
||||||
|
:hint "invalid file uploaded"))))
|
||||||
|
|
||||||
(defn file-data-handler
|
(defn raw-export-import-handler
|
||||||
[cfg request]
|
[cfg request]
|
||||||
(case (yreq/method request)
|
(case (yreq/method request)
|
||||||
:get (retrieve-file-data cfg request)
|
:get (download-file-data cfg request)
|
||||||
:post (upload-file-data cfg request)
|
:post (upload-file-data cfg request)
|
||||||
(ex/raise :type :http
|
(ex/raise :type :http
|
||||||
:code :method-not-found)))
|
:code :method-not-found)))
|
||||||
|
|
||||||
(defn file-changes-handler
|
|
||||||
[{:keys [::db/pool]} {:keys [params] :as request}]
|
|
||||||
(letfn [(retrieve-changes [file-id revn]
|
|
||||||
(if (str/includes? revn ":")
|
|
||||||
(let [[start end] (->> (str/split revn #":")
|
|
||||||
(map str/trim)
|
|
||||||
(map parse-long))]
|
|
||||||
(some->> (db/exec! pool [sql:retrieve-range-of-changes file-id start end])
|
|
||||||
(map :changes)
|
|
||||||
(map blob/decode)
|
|
||||||
(mapcat identity)
|
|
||||||
(vec)))
|
|
||||||
|
|
||||||
(if-let [revn (parse-long revn)]
|
|
||||||
(let [item (db/exec-one! pool [sql:retrieve-single-change file-id revn])]
|
|
||||||
(some-> item :changes blob/decode vec))
|
|
||||||
(ex/raise :type :validation :code :invalid-arguments))))]
|
|
||||||
|
|
||||||
(let [file-id (some-> params :id parse-uuid)
|
|
||||||
revn (or (some-> params :revn parse-long) "latest")
|
|
||||||
filename (str file-id)]
|
|
||||||
|
|
||||||
(when (or (not file-id) (not revn))
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :invalid-arguments
|
|
||||||
:hint "missing arguments"))
|
|
||||||
|
|
||||||
(let [data (retrieve-changes file-id revn)]
|
|
||||||
(if (contains? params :download)
|
|
||||||
(prepare-download-response data filename)
|
|
||||||
(prepare-response data))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; ERROR BROWSER
|
;; ERROR BROWSER
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -430,49 +418,49 @@
|
||||||
::yres/body "OK"}))
|
::yres/body "OK"}))
|
||||||
|
|
||||||
|
|
||||||
(defn- add-team-feature
|
(defn- handle-team-features
|
||||||
[{:keys [params] :as request}]
|
[cfg {:keys [params] :as request}]
|
||||||
(let [team-id (some-> params :team-id d/parse-uuid)
|
(let [team-id (some-> params :team-id d/parse-uuid)
|
||||||
feature (some-> params :feature str)
|
feature (some-> params :feature str)
|
||||||
|
action (some-> params :action)
|
||||||
skip-check (contains? params :skip-check)]
|
skip-check (contains? params :skip-check)]
|
||||||
|
|
||||||
(when-not (contains? params :force)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :missing-force
|
|
||||||
:hint "missing force checkbox"))
|
|
||||||
|
|
||||||
(when (nil? team-id)
|
(when (nil? team-id)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :invalid-team-id
|
:code :invalid-team-id
|
||||||
:hint "provided invalid team id"))
|
:hint "provided invalid team id"))
|
||||||
|
|
||||||
|
(if (= action "show")
|
||||||
|
(let [team (db/run! cfg teams/get-team-info {:id team-id})]
|
||||||
|
{::yres/status 200
|
||||||
|
::yres/headers {"content-type" "text/plain"}
|
||||||
|
::yres/body (apply str "Team features:\n"
|
||||||
|
(->> (:features team)
|
||||||
|
(map (fn [feature]
|
||||||
|
(str "- " feature "\n")))))})
|
||||||
|
|
||||||
|
(do
|
||||||
|
(when-not (contains? params :force)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :missing-force
|
||||||
|
:hint "missing force checkbox"))
|
||||||
|
|
||||||
|
(cond
|
||||||
|
(= action "enable")
|
||||||
(srepl/enable-team-feature! team-id feature :skip-check skip-check)
|
(srepl/enable-team-feature! team-id feature :skip-check skip-check)
|
||||||
|
|
||||||
{::yres/status 200
|
(= action "disable")
|
||||||
::yres/headers {"content-type" "text/plain"}
|
|
||||||
::yres/body "OK"}))
|
|
||||||
|
|
||||||
(defn- remove-team-feature
|
|
||||||
[{:keys [params] :as request}]
|
|
||||||
(let [team-id (some-> params :team-id d/parse-uuid)
|
|
||||||
feature (some-> params :feature str)
|
|
||||||
skip-check (contains? params :skip-check)]
|
|
||||||
|
|
||||||
(when-not (contains? params :force)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :missing-force
|
|
||||||
:hint "missing force checkbox"))
|
|
||||||
|
|
||||||
(when (nil? team-id)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :invalid-team-id
|
|
||||||
:hint "provided invalid team id"))
|
|
||||||
|
|
||||||
(srepl/disable-team-feature! team-id feature :skip-check skip-check)
|
(srepl/disable-team-feature! team-id feature :skip-check skip-check)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :invalid-action
|
||||||
|
:hint (str "invalid action: " action)))
|
||||||
|
|
||||||
|
|
||||||
{::yres/status 200
|
{::yres/status 200
|
||||||
::yres/headers {"content-type" "text/plain"}
|
::yres/headers {"content-type" "text/plain"}
|
||||||
::yres/body "OK"}))
|
::yres/body "OK"}))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; OTHER SMALL VIEWS/HANDLERS
|
;; OTHER SMALL VIEWS/HANDLERS
|
||||||
|
@ -525,6 +513,25 @@
|
||||||
(ex/raise :type :authentication
|
(ex/raise :type :authentication
|
||||||
:code :only-admins-allowed)))))})
|
:code :only-admins-allowed)))))})
|
||||||
|
|
||||||
|
(def errors
|
||||||
|
(letfn [(handle-error [cause]
|
||||||
|
(when-let [data (ex-data cause)]
|
||||||
|
(when (= :validation (:type data))
|
||||||
|
(str "Error: " (or (:hint data) (ex-message cause)) "\n"))))]
|
||||||
|
{:name ::errors
|
||||||
|
:compile
|
||||||
|
(fn [& _params]
|
||||||
|
(fn [handler]
|
||||||
|
(fn [request]
|
||||||
|
(try
|
||||||
|
(handler request)
|
||||||
|
(catch Throwable cause
|
||||||
|
(let [body (or (handle-error cause)
|
||||||
|
(ex/format-throwable cause))]
|
||||||
|
{::yres/status 400
|
||||||
|
::yres/headers {"content-type" "text/plain"}
|
||||||
|
::yres/body body}))))))}))
|
||||||
|
|
||||||
(defmethod ig/assert-key ::routes
|
(defmethod ig/assert-key ::routes
|
||||||
[_ params]
|
[_ params]
|
||||||
(assert (db/pool? (::db/pool params)) "expected a valid database pool")
|
(assert (db/pool? (::db/pool params)) "expected a valid database pool")
|
||||||
|
@ -540,15 +547,14 @@
|
||||||
["/changelog" {:handler (partial changelog-handler cfg)}]
|
["/changelog" {:handler (partial changelog-handler cfg)}]
|
||||||
["/error/:id" {:handler (partial error-handler cfg)}]
|
["/error/:id" {:handler (partial error-handler cfg)}]
|
||||||
["/error" {:handler (partial error-list-handler cfg)}]
|
["/error" {:handler (partial error-list-handler cfg)}]
|
||||||
["/actions/resend-email-verification"
|
["/actions" {:middleware [[errors]]}
|
||||||
|
["/resend-email-verification"
|
||||||
{:handler (partial resend-email-notification cfg)}]
|
{:handler (partial resend-email-notification cfg)}]
|
||||||
["/actions/reset-file-version"
|
["/reset-file-version"
|
||||||
{:handler (partial reset-file-version cfg)}]
|
{:handler (partial reset-file-version cfg)}]
|
||||||
["/actions/add-team-feature"
|
["/handle-team-features"
|
||||||
{:handler (partial add-team-feature)}]
|
{:handler (partial handle-team-features cfg)}]
|
||||||
["/actions/remove-team-feature"
|
["/file-export" {:handler (partial export-handler cfg)}]
|
||||||
{:handler (partial remove-team-feature)}]
|
["/file-import" {:handler (partial import-handler cfg)}]
|
||||||
["/file/export" {:handler (partial export-handler cfg)}]
|
["/file-raw-export-import" {:handler (partial raw-export-import-handler cfg)}]]]])
|
||||||
["/file/import" {:handler (partial import-handler cfg)}]
|
|
||||||
["/file/data" {:handler (partial file-data-handler cfg)}]
|
|
||||||
["/file/changes" {:handler (partial file-changes-handler cfg)}]]])
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
(ns app.rpc.commands.files-create
|
(ns app.rpc.commands.files-create
|
||||||
(:require
|
(:require
|
||||||
[app.binfile.common :as bfc]
|
[app.binfile.common :as bfc]
|
||||||
[app.common.data.macros :as dm]
|
|
||||||
[app.common.features :as cfeat]
|
[app.common.features :as cfeat]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.types.file :as ctf]
|
[app.common.types.file :as ctf]
|
||||||
|
@ -41,9 +40,7 @@
|
||||||
:or {is-shared false revn 0 create-page true}
|
:or {is-shared false revn 0 create-page true}
|
||||||
:as params}]
|
:as params}]
|
||||||
|
|
||||||
(dm/assert!
|
(assert (db/connection? conn) "expected a valid connection")
|
||||||
"expected a valid connection"
|
|
||||||
(db/connection? conn))
|
|
||||||
|
|
||||||
(binding [pmap/*tracked* (pmap/create-tracked)
|
(binding [pmap/*tracked* (pmap/create-tracked)
|
||||||
cfeat/*current* features]
|
cfeat/*current* features]
|
||||||
|
|
|
@ -78,9 +78,10 @@
|
||||||
|
|
||||||
(defn decode-row
|
(defn decode-row
|
||||||
[{:keys [features subscription] :as row}]
|
[{:keys [features subscription] :as row}]
|
||||||
|
(when row
|
||||||
(cond-> row
|
(cond-> row
|
||||||
(some? features) (assoc :features (db/decode-pgarray features #{}))
|
(some? features) (assoc :features (db/decode-pgarray features #{}))
|
||||||
(some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription))))
|
(some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription)))))
|
||||||
|
|
||||||
;; FIXME: move
|
;; FIXME: move
|
||||||
|
|
||||||
|
@ -461,11 +462,12 @@
|
||||||
|
|
||||||
;; --- COMMAND QUERY: get-team-info
|
;; --- COMMAND QUERY: get-team-info
|
||||||
|
|
||||||
(defn- get-team-info
|
(defn get-team-info
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [id] :as params}]
|
[{:keys [::db/conn] :as cfg} {:keys [id] :as params}]
|
||||||
(db/get* conn :team
|
(-> (db/get* conn :team
|
||||||
{:id id}
|
{:id id}
|
||||||
{::sql/columns [:id :is-default]}))
|
{::sql/columns [:id :is-default :features]})
|
||||||
|
(decode-row)))
|
||||||
|
|
||||||
(sv/defmethod ::get-team-info
|
(sv/defmethod ::get-team-info
|
||||||
"Retrieve minimal team info by its ID."
|
"Retrieve minimal team info by its ID."
|
||||||
|
|
|
@ -9,17 +9,16 @@
|
||||||
data resources."
|
data resources."
|
||||||
(:refer-clojure :exclude [read-string hash-map merge name update-vals
|
(:refer-clojure :exclude [read-string hash-map merge name update-vals
|
||||||
parse-double group-by iteration concat mapcat
|
parse-double group-by iteration concat mapcat
|
||||||
parse-uuid max min regexp?])
|
parse-uuid max min regexp? array?])
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(:require-macros [app.common.data]))
|
(:require-macros [app.common.data]))
|
||||||
|
|
||||||
(:require
|
(:require
|
||||||
#?(:cljs [cljs.core :as c]
|
|
||||||
:clj [clojure.core :as c])
|
|
||||||
#?(:cljs [cljs.reader :as r]
|
#?(:cljs [cljs.reader :as r]
|
||||||
:clj [clojure.edn :as r])
|
:clj [clojure.edn :as r])
|
||||||
#?(:cljs [goog.array :as garray])
|
#?(:cljs [goog.array :as garray])
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
|
[clojure.core :as c]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[linked.map :as lkm]
|
[linked.map :as lkm]
|
||||||
|
@ -167,6 +166,15 @@
|
||||||
;; Data Structures Access & Manipulation
|
;; Data Structures Access & Manipulation
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn array?
|
||||||
|
[o]
|
||||||
|
#?(:cljs
|
||||||
|
(c/array? o)
|
||||||
|
:clj
|
||||||
|
(if (some? o)
|
||||||
|
(.isArray (class o))
|
||||||
|
false)))
|
||||||
|
|
||||||
(defn not-empty?
|
(defn not-empty?
|
||||||
[coll]
|
[coll]
|
||||||
(boolean (seq coll)))
|
(boolean (seq coll)))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue