From 7e493376a4f936dbf7298eaef2c77fb83b44d5a1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Jul 2025 08:50:43 +0200 Subject: [PATCH 1/3] :sparkles: Reuse file data checkers on file validate ns --- common/src/app/common/files/validate.cljc | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 5bdb410669..0fe15a067c 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -650,26 +650,12 @@ (check-component component file) (deref *errors*))) -(def ^:private valid-fdata? - "Structural validation of file data using defined schema" - (sm/lazy-validator ::ctf/data)) - -(def ^:private get-fdata-explain - "Get schema explain data for file data" - (sm/lazy-explainer ::ctf/data)) - (defn validate-file-schema! "Validates the file itself, without external dependencies, it performs the schema checking and some semantical validation of the content." - [{:keys [id data] :as file}] - (when-not (valid-fdata? data) - (ex/raise :type :validation - :code :schema-validation - :hint (str/ffmt "invalid file data structure found on file '%'" id) - :file-id id - ::sm/explain (get-fdata-explain data))) - file) + [file] + (update file :data ctf/check-file-data)) (defn validate-file! "Validate full referential integrity and semantic coherence on file data. From ea0044f69a7f8f5a287b721278eb49d1c236e319 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Jul 2025 08:51:19 +0200 Subject: [PATCH 2/3] :lipstick: Use resolved schemas instead of references For several schemas on common types --- common/src/app/common/files/validate.cljc | 1 - common/src/app/common/types/container.cljc | 29 +++++++++++----------- common/src/app/common/types/file.cljc | 14 +++++------ common/src/app/common/types/page.cljc | 4 +-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 0fe15a067c..75650ca97d 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -669,7 +669,6 @@ :file-id (:id file) :details errors))) - (declare compare-slots) ;; Optional check to look for missing swap slots. diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 89e210d871..6c7e468314 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -15,7 +15,7 @@ [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.pages-list :as ctpl] - [app.common.types.plugins :as ctpg] + [app.common.types.plugins :refer [schema:plugin-data]] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] [app.common.types.text :as cttx] @@ -30,21 +30,22 @@ (def valid-container-types #{:page :component}) -(sm/register! - ^{::sm/type ::container} - [:map - [:id ::sm/uuid] - [:type {:optional true} - [::sm/one-of valid-container-types]] - [:name :string] - [:path {:optional true} [:maybe :string]] - [:modified-at {:optional true} ::sm/inst] - [:objects {:optional true} - [:map-of {:gen/max 10} ::sm/uuid :map]] - [:plugin-data {:optional true} ::ctpg/plugin-data]]) +(def schema:container + (sm/register! + ^{::sm/type ::container} + [:map + [:id ::sm/uuid] + [:type {:optional true} + [::sm/one-of valid-container-types]] + [:name :string] + [:path {:optional true} [:maybe :string]] + [:modified-at {:optional true} ::sm/inst] + [:objects {:optional true} + [:map-of {:gen/max 10} ::sm/uuid :map]] + [:plugin-data {:optional true} schema:plugin-data]])) (def check-container - (sm/check-fn ::container)) + (sm/check-fn schema:container)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HELPERS diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 521915da96..651b1b58c4 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -23,9 +23,9 @@ [app.common.types.container :as ctn] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] - [app.common.types.plugins :as ctpg] + [app.common.types.plugins :refer [schema:plugin-data]] [app.common.types.shape-tree :as ctst] - [app.common.types.tokens-lib :as ctl] + [app.common.types.tokens-lib :refer [schema:tokens-lib]] [app.common.types.typographies-list :as ctyl] [app.common.types.typography :as cty] [app.common.uuid :as uuid] @@ -61,13 +61,13 @@ [:map-of {:gen/max 5} ::sm/uuid ctc/schema:library-color]) (def schema:components - [:map-of {:gen/max 5} ::sm/uuid ::ctn/container]) + [:map-of {:gen/max 5} ::sm/uuid ctn/schema:container]) (def schema:typographies - [:map-of {:gen/max 2} ::sm/uuid ::cty/typography]) + [:map-of {:gen/max 2} ::sm/uuid cty/schema:typography]) (def schema:pages-index - [:map-of {:gen/max 5} ::sm/uuid ::ctp/page]) + [:map-of {:gen/max 5} ::sm/uuid ctp/schema:page]) (def schema:options [:map {:title "FileOptions"} @@ -82,8 +82,8 @@ [:colors {:optional true} schema:colors] [:components {:optional true} schema:components] [:typographies {:optional true} schema:typographies] - [:plugin-data {:optional true} ::ctpg/plugin-data] - [:tokens-lib {:optional true} ::ctl/tokens-lib]]) + [:plugin-data {:optional true} schema:plugin-data] + [:tokens-lib {:optional true} schema:tokens-lib]]) (def schema:file "A schema for validate a file data structure; data is optional diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 230f7460d7..ee6084bd66 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -53,10 +53,10 @@ [:name :string] [:index {:optional true} ::sm/int] [:objects schema:objects] - [:default-grids {:optional true} ::ctg/default-grids] + [:default-grids {:optional true} ctg/schema:default-grids] [:flows {:optional true} schema:flows] [:guides {:optional true} schema:guides] - [:plugin-data {:optional true} ::ctpg/plugin-data] + [:plugin-data {:optional true} ctpg/schema:plugin-data] [:background {:optional true} ctc/schema:hex-color] [:comment-thread-positions {:optional true} From fa72bb4adfce662189fb37eb87498f84cc78a2c0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Jul 2025 13:17:03 +0200 Subject: [PATCH 3/3] :sparkles: Add several improvements to admin pannel --- backend/resources/app/templates/debug.tmpl | 158 ++++------ .../resources/app/templates/error-list.tmpl | 4 +- backend/src/app/binfile/common.clj | 4 +- backend/src/app/features/file_migrations.clj | 6 + backend/src/app/http/debug.clj | 278 +++++++++--------- backend/src/app/rpc/commands/files_create.clj | 5 +- backend/src/app/rpc/commands/teams.clj | 16 +- common/src/app/common/data.cljc | 14 +- 8 files changed, 233 insertions(+), 252 deletions(-) diff --git a/backend/resources/app/templates/debug.tmpl b/backend/resources/app/templates/debug.tmpl index a630e8fbd2..65d3d7614c 100644 --- a/backend/resources/app/templates/debug.tmpl +++ b/backend/resources/app/templates/debug.tmpl @@ -17,38 +17,6 @@ Debug Main Page CLICK HERE TO SEE THE ERROR REPORTS -
- Download file data: - Given an FILE-ID, downloads the file data as file. The file data is encoded using transit. -
-
- -
-
- - -
-
-
- -
- Upload File Data: - Create a new file on your draft projects using the file downloaded from the previous section. -
-
- -
-
- - -
- -
- -
-
-
-
Profile Management
@@ -81,6 +49,50 @@ Debug Main Page +
+ +
+ Download RAW file data: + Given an FILE-ID, downloads the file AS-IS (no validation + checks, just exports the file data and related objects in raw) + +
+
+ WARNING: this operation does not performs any checks +
+ +
+ +
+
+ + +
+ +
+ +
+ Upload File Data: + Create a new file on your draft projects using the file downloaded from the previous section. +
+
+ WARNING: this operation does not performs any checks +
+
+
+ +
+
+ + +
+ +
+ +
+
+
+
Export binfile: @@ -88,7 +100,7 @@ Debug Main Page the related libraries in a single custom formatted binary file. -
+
@@ -116,7 +128,7 @@ Debug Main Page Import binfile: Import penpot file in binary format. - +
@@ -130,79 +142,27 @@ Debug Main Page
- Reset file version - Allows reset file data version to a specific number/ - - -
- -
-
- -
- -
- - -
- - This is a just a security double check for prevent non intentional submits. - -
- - -
- -
- -
-
- -
-

Feature Flags

-
- Enable + Feature Flags for Team Add a feature flag to a team -
+
- +
- - -
- - Do not check if the feature is supported - -
- -
- - -
- - This is a just a security double check for prevent non intentional submits. - -
- -
- -
-
-
-
- Disable - Remove a feature flag from a team -
-
- -
-
- +
diff --git a/backend/resources/app/templates/error-list.tmpl b/backend/resources/app/templates/error-list.tmpl index b8af22fb89..c328700ecf 100644 --- a/backend/resources/app/templates/error-list.tmpl +++ b/backend/resources/app/templates/error-list.tmpl @@ -7,7 +7,9 @@ penpot - error list {% block content %}
diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj index b1a131ef10..072327792b 100644 --- a/backend/src/app/binfile/common.clj +++ b/backend/src/app/binfile/common.clj @@ -155,7 +155,7 @@ (defn decode-file "A general purpose file decoding function that resolves all external 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)] (let [file (->> file (feat.fmigr/resolve-applied-migrations cfg) @@ -168,7 +168,7 @@ (update :data feat.fdata/process-pointers deref) (update :data feat.fdata/process-objects (partial into {})) (update :data assoc :id id) - (fmg/migrate-file libs))))) + (cond-> migrate? (fmg/migrate-file libs)))))) (defn get-file "Get file, resolve all features and apply migrations. diff --git a/backend/src/app/features/file_migrations.clj b/backend/src/app/features/file_migrations.clj index beec865555..9552d78ba4 100644 --- a/backend/src/app/features/file_migrations.clj +++ b/backend/src/app/features/file_migrations.clj @@ -37,3 +37,9 @@ {::db/return-keys false ::sql/on-conflict-do-nothing true}) (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)) diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 5e05962566..f4d835b417 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -15,9 +15,11 @@ [app.common.features :as cfeat] [app.common.logging :as l] [app.common.pprint :as pp] + [app.common.transit :as t] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] + [app.features.file-migrations :as feat.fmig] [app.http.session :as session] [app.rpc.commands.auth :as auth] [app.rpc.commands.files-create :refer [create-file]] @@ -50,26 +52,26 @@ {::yres/status 200 ::yres/headers {"content-type" "text/html"} ::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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn prepare-response - [body] - (let [headers {"content-type" "application/transit+json"}] - {::yres/status 200 - ::yres/body body - ::yres/headers headers})) +(defn- get-resolved-file + [cfg file-id] + (some-> (bfc/get-file cfg file-id :migrate? false) + (update :data blob/encode))) -(defn prepare-download-response - [body filename] - (let [headers {"content-disposition" (str "attachment; filename=" filename) - "content-type" "application/octet-stream"}] - {::yres/status 200 - ::yres/body body - ::yres/headers headers})) +(defn prepare-download + [file filename] + {::yres/status 200 + ::yres/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 "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 "select revn, changes, data from file_change where file_id=? and revn = ?") -(defn- retrieve-file-data - [{:keys [::db/pool]} {:keys [params ::session/profile-id] :as request}] +(defn- download-file-data + [cfg {:keys [params ::session/profile-id] :as request}] (let [file-id (some-> params :file-id parse-uuid) - revn (some-> params :revn parse-long) filename (str file-id)] (when-not file-id (ex/raise :type :validation :code :missing-arguments)) - (let [data (if (integer? revn) - (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")) + (if-let [file (get-resolved-file cfg file-id)] (cond (contains? params :download) - (prepare-download-response data filename) + (prepare-download file filename) (contains? params :clone) - (let [profile (profile/get-profile pool profile-id) - project-id (:default-project-id profile)] + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (let [profile (profile/get-profile conn profile-id) + project-id (:default-project-id profile) + file (-> (create-file cfg {:id (uuid/next) + :name (str "Cloned: " (:name file)) + :features (:features file) + :project-id project-id + :profile-id profile-id}) + (assoc :data (:data file)) + (assoc :migrations (:migrations file)))] - (db/run! pool (fn [{:keys [::db/conn] :as cfg}] - (create-file cfg {:id file-id - :name (str "Cloned file: " filename) - :project-id project-id - :profile-id profile-id}) - (db/update! conn :file - {:data data} - {:id file-id}) - {::yres/status 201 - ::yres/body "OK CREATED"}))) + (feat.fmig/reset-migrations! conn file) + (db/update! conn :file + {:data (:data file)} + {:id (:id file)} + {::db/return-keys false}) + + + {::yres/status 201 + ::yres/body "OK CLONED"}))) :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? [pool id] @@ -123,81 +131,61 @@ (-> (db/exec-one! pool [sql id]) :exists))) (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) 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) - (let [fname (str "Imported file *: " (dt/now)) + (if (and file project-id) + (let [fname (str "Imported: " (:name file) "(" (dt/now) ")") reuse-id? (contains? params :reuseid) file-id (or (and reuse-id? (ex/ignoring (-> params :file :filename parse-uuid))) (uuid/next))] (if (and reuse-id? file-id (is-file-exists? pool file-id)) - (do - (db/update! pool :file - {:data data - :deleted-at nil} - {:id file-id}) - {::yres/status 200 - ::yres/body "OK UPDATED"}) + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (db/update! conn :file + {:data (:data file) + :features (into-array (:features file)) + :deleted-at nil} + {:id file-id} + {::db/return-keys false}) + (feat.fmig/reset-migrations! conn file) + {::yres/status 200 + ::yres/body "OK UPDATED"})) + + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (let [file (-> (create-file cfg {:id file-id + :name fname + :features (:features file) + :project-id project-id + :profile-id profile-id}) + (assoc :data (:data file)) + (assoc :migrations (:migrations file)))] - (db/run! pool (fn [{:keys [::db/conn] :as cfg}] - (create-file cfg {:id file-id - :name fname - :project-id project-id - :profile-id profile-id}) (db/update! conn :file - {:data data} - {:id file-id}) + {:data (:data file)} + {:id file-id} + {::db/return-keys false}) + (feat.fmig/reset-migrations! conn file) {::yres/status 201 - ::yres/body "OK CREATED"})))) + ::yres/body "OK CREATED"}))))) - {::yres/status 500 - ::yres/body "ERROR"}))) + (ex/raise :type :validation + :code :invalid-params + :hint "invalid file uploaded")))) -(defn file-data-handler +(defn raw-export-import-handler [cfg request] (case (yreq/method request) - :get (retrieve-file-data cfg request) + :get (download-file-data cfg request) :post (upload-file-data cfg request) (ex/raise :type :http :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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -430,49 +418,49 @@ ::yres/body "OK"})) -(defn- add-team-feature - [{:keys [params] :as request}] - (let [team-id (some-> params :team-id d/parse-uuid) - feature (some-> params :feature str) +(defn- handle-team-features + [cfg {:keys [params] :as request}] + (let [team-id (some-> params :team-id d/parse-uuid) + feature (some-> params :feature str) + action (some-> params :action) 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/enable-team-feature! team-id feature :skip-check skip-check) + (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")))))}) - {::yres/status 200 - ::yres/headers {"content-type" "text/plain"} - ::yres/body "OK"})) + (do + (when-not (contains? params :force) + (ex/raise :type :validation + :code :missing-force + :hint "missing force checkbox")) -(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)] + (cond + (= action "enable") + (srepl/enable-team-feature! team-id feature :skip-check skip-check) - (when-not (contains? params :force) - (ex/raise :type :validation - :code :missing-force - :hint "missing force checkbox")) + (= action "disable") + (srepl/disable-team-feature! team-id feature :skip-check skip-check) - (when (nil? team-id) - (ex/raise :type :validation - :code :invalid-team-id - :hint "provided invalid team id")) + :else + (ex/raise :type :validation + :code :invalid-action + :hint (str "invalid action: " action))) - (srepl/disable-team-feature! team-id feature :skip-check skip-check) - {::yres/status 200 - ::yres/headers {"content-type" "text/plain"} - ::yres/body "OK"})) + {::yres/status 200 + ::yres/headers {"content-type" "text/plain"} + ::yres/body "OK"})))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; OTHER SMALL VIEWS/HANDLERS @@ -525,6 +513,25 @@ (ex/raise :type :authentication :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 [_ params] (assert (db/pool? (::db/pool params)) "expected a valid database pool") @@ -540,15 +547,14 @@ ["/changelog" {:handler (partial changelog-handler cfg)}] ["/error/:id" {:handler (partial error-handler cfg)}] ["/error" {:handler (partial error-list-handler cfg)}] - ["/actions/resend-email-verification" - {:handler (partial resend-email-notification cfg)}] - ["/actions/reset-file-version" - {:handler (partial reset-file-version cfg)}] - ["/actions/add-team-feature" - {:handler (partial add-team-feature)}] - ["/actions/remove-team-feature" - {:handler (partial remove-team-feature)}] - ["/file/export" {:handler (partial export-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)}]]]) + ["/actions" {:middleware [[errors]]} + ["/resend-email-verification" + {:handler (partial resend-email-notification cfg)}] + ["/reset-file-version" + {:handler (partial reset-file-version cfg)}] + ["/handle-team-features" + {:handler (partial handle-team-features cfg)}] + ["/file-export" {:handler (partial export-handler cfg)}] + ["/file-import" {:handler (partial import-handler cfg)}] + ["/file-raw-export-import" {:handler (partial raw-export-import-handler cfg)}]]]]) + diff --git a/backend/src/app/rpc/commands/files_create.clj b/backend/src/app/rpc/commands/files_create.clj index fc9dcb7fef..a4c9069e07 100644 --- a/backend/src/app/rpc/commands/files_create.clj +++ b/backend/src/app/rpc/commands/files_create.clj @@ -7,7 +7,6 @@ (ns app.rpc.commands.files-create (:require [app.binfile.common :as bfc] - [app.common.data.macros :as dm] [app.common.features :as cfeat] [app.common.schema :as sm] [app.common.types.file :as ctf] @@ -41,9 +40,7 @@ :or {is-shared false revn 0 create-page true} :as params}] - (dm/assert! - "expected a valid connection" - (db/connection? conn)) + (assert (db/connection? conn) "expected a valid connection") (binding [pmap/*tracked* (pmap/create-tracked) cfeat/*current* features] diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index bb23c6997c..a099223b83 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -78,9 +78,10 @@ (defn decode-row [{:keys [features subscription] :as row}] - (cond-> row - (some? features) (assoc :features (db/decode-pgarray features #{})) - (some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription)))) + (when row + (cond-> row + (some? features) (assoc :features (db/decode-pgarray features #{})) + (some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription))))) ;; FIXME: move @@ -461,11 +462,12 @@ ;; --- COMMAND QUERY: get-team-info -(defn- get-team-info +(defn get-team-info [{:keys [::db/conn] :as cfg} {:keys [id] :as params}] - (db/get* conn :team - {:id id} - {::sql/columns [:id :is-default]})) + (-> (db/get* conn :team + {:id id} + {::sql/columns [:id :is-default :features]}) + (decode-row))) (sv/defmethod ::get-team-info "Retrieve minimal team info by its ID." diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index bb25641333..d06da2ae4c 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -9,17 +9,16 @@ data resources." (:refer-clojure :exclude [read-string hash-map merge name update-vals parse-double group-by iteration concat mapcat - parse-uuid max min regexp?]) + parse-uuid max min regexp? array?]) #?(:cljs (:require-macros [app.common.data])) (:require - #?(:cljs [cljs.core :as c] - :clj [clojure.core :as c]) #?(:cljs [cljs.reader :as r] :clj [clojure.edn :as r]) #?(:cljs [goog.array :as garray]) [app.common.math :as mth] + [clojure.core :as c] [clojure.set :as set] [cuerdas.core :as str] [linked.map :as lkm] @@ -167,6 +166,15 @@ ;; Data Structures Access & Manipulation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn array? + [o] + #?(:cljs + (c/array? o) + :clj + (if (some? o) + (.isArray (class o)) + false))) + (defn not-empty? [coll] (boolean (seq coll)))