From 8b902e38d28c7f0aa2040a6b75bdd3830d056537 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Aug 2026 10:38:16 +0000 Subject: [PATCH] :sparkles: Persist binfile manifest metadata in file_data on import Store the generated-by and referer fields from the v3 binfile manifest into the existing file_data.metadata JSONB column during import. This preserves the export source information for diagnostics and analytics. The metadata schema in fdata.clj is extended with optional :generated-by and :referer string fields. The storage backend persistence now merges metadata instead of overwriting it, so manifest fields coexist with :storage-ref-id. Closes #11106 AI-assisted-by: mimo-v2.5-pro --- backend/src/app/binfile/common.clj | 1 + backend/src/app/binfile/v3.clj | 8 +++++--- backend/src/app/features/fdata.clj | 19 +++++++------------ backend/src/app/rpc.clj | 1 + backend/src/app/rpc/commands/binfile.clj | 2 +- common/src/app/common/types/file.cljc | 10 ++++++++++ 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj index f984a98550..3e4402be92 100644 --- a/backend/src/app/binfile/common.clj +++ b/backend/src/app/binfile/common.clj @@ -723,6 +723,7 @@ (-> (select-keys file file-attrs) (assoc :data nil) (dissoc :team-id) + (dissoc :metadata) (dissoc :migrations))) (defn- file->file-data-params diff --git a/backend/src/app/binfile/v3.clj b/backend/src/app/binfile/v3.clj index 952cb69e8f..8b4cb9b4bb 100644 --- a/backend/src/app/binfile/v3.clj +++ b/backend/src/app/binfile/v3.clj @@ -392,7 +392,7 @@ params {:type "penpot/export-files" :version 1 :generated-by (str "penpot/" (:full cf/version)) - :refer "penpot" + :referer "penpot" :files (vec (vals files)) :relations rels}] (write-entry! output "manifest.json" params)))) @@ -734,7 +734,7 @@ :plugin-data plugin-data})) (defn- import-file - [{:keys [::db/conn ::bfc/project-id] :as cfg} {file-id :id file-name :name}] + [{:keys [::db/conn ::bfc/project-id ::manifest] :as cfg} {file-id :id file-name :name}] (let [file-id' (bfc/lookup-index file-id) file (read-file cfg file-id) media (read-file-media cfg file-id) @@ -801,8 +801,10 @@ (assoc :data data) (assoc :name file-name) (assoc :project-id project-id) + (assoc :metadata (d/without-nils + {:generated-by (get manifest :generated-by) + :referer (get manifest :referer)})) (dissoc :options)) - file (bfc/process-file cfg file) file (ctf/check-file file)] diff --git a/backend/src/app/features/fdata.clj b/backend/src/app/features/fdata.clj index 412ca223cf..759c132980 100644 --- a/backend/src/app/features/fdata.clj +++ b/backend/src/app/features/fdata.clj @@ -12,6 +12,7 @@ [app.common.logging :as l] [app.common.schema :as sm] [app.common.time :as ct] + [app.common.types.file :as ctf] [app.common.types.objects-map :as omap] [app.config :as cf] [app.db :as db] @@ -130,6 +131,7 @@ metadata (some-> metadata db/json) modified-at (or modified-at created-at)] + (db/exec-one! cfg [sql:upsert-file-data file-id id created-at @@ -159,15 +161,15 @@ :content-type "application/octet-stream" :file-id file-id :id id}) - metadata {:storage-ref-id (:id sobject)} + metadata (-> (:metadata params) + (assoc :storage-ref-id (:id sobject))) params (-> params (assoc :metadata metadata) (assoc :data nil))] (upsert-in-database cfg params)) (= backend "db") - (->> (dissoc params :metadata) - (upsert-in-database cfg)) + (upsert-in-database cfg params) (= backend "legacy-db") (cond @@ -213,18 +215,11 @@ [backend] (or backend (cf/get :file-data-backend))) -(def ^:private schema:metadata - [:map {:title "Metadata"} - [:storage-ref-id {:optional true} ::sm/uuid]]) - -(def decode-metadata-with-schema - (sm/decoder schema:metadata sm/json-transformer)) - (defn decode-metadata [metadata] (some-> metadata (db/decode-json-pgobject) - (decode-metadata-with-schema))) + (ctf/decode-file-metadata))) (def ^:private schema:update-params [:map {:closed true} @@ -232,7 +227,7 @@ [:type [:enum "main" "snapshot" "fragment"]] [:file-id ::sm/uuid] [:backend {:optional true} [:enum "db" "legacy-db" "storage"]] - [:metadata {:optional true} [:maybe schema:metadata]] + [:metadata {:optional true} [:maybe ctf/schema:file-metadata]] [:data {:optional true} bytes?] [:created-at {:optional true} ::ct/inst] [:modified-at {:optional true} [:maybe ::ct/inst]] diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index c7e33312ca..af13ebd795 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -236,6 +236,7 @@ explain (sm/explainer schema) decode (sm/decoder schema sm/json-transformer) encode (sm/encoder schema sm/json-transformer)] + (fn [cfg params] (let [params (decode params)] (if (validate params) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 78ba08e5fd..e68210aae3 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -118,7 +118,7 @@ (def ^:private schema:import-binfile [:and - [:map {:title "import-binfile" :closed true} + [:map {:title "import-binfile"} [:name [:or [:string {:max 250}] [:map-of ::sm/uuid [:string {:max 250}]]]] [:project-id ::sm/uuid] diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index fe91dca8ad..d7e9feb676 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -88,6 +88,12 @@ [:plugin-data {:optional true} schema:plugin-data] [:tokens-lib {:optional true} schema:tokens-lib]]) +(def schema:file-metadata + [:map {:title "Metadata"} + [:storage-ref-id {:optional true} ::sm/uuid] + [:generated-by {:optional true} :string] + [:referer {:optional true} :string]]) + (def schema:file "A schema for validate a file data structure; data is optional because sometimes we want to validate file without the data." @@ -106,6 +112,7 @@ [:data {:optional true} schema:data] [:version :int] [:features ::cfeat/features] + [:metadata {:optional true} schema:file-metadata] [:migrations {:optional true} [::sm/set {:ordered true} :string]]]) @@ -123,6 +130,9 @@ (def check-file-media (sm/check-fn schema:media)) +(def decode-file-metadata + (sm/decoder schema:file-metadata sm/json-transformer)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; INITIALIZATION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;