diff --git a/backend/resources/app/templates/api-doc-entry.tmpl b/backend/resources/app/templates/api-doc-entry.tmpl
index bd3af445f..c61157ec2 100644
--- a/backend/resources/app/templates/api-doc-entry.tmpl
+++ b/backend/resources/app/templates/api-doc-entry.tmpl
@@ -22,11 +22,7 @@
{% endif %}
{% if item.params-schema-js %}
- SC
-
- {% else %}
-
- SP
+ SCHEMA
{% endif %}
diff --git a/backend/resources/app/templates/api-doc.tmpl b/backend/resources/app/templates/api-doc.tmpl
index 2a0873973..4a4082537 100644
--- a/backend/resources/app/templates/api-doc.tmpl
+++ b/backend/resources/app/templates/api-doc.tmpl
@@ -38,7 +38,7 @@
GENERAL NOTES
Authentication
- The penpot backend right now offerts two way for authenticate the request:
+
The penpot backend right now offers two way for authenticate the request:
cookies (the same mechanism that we use ourselves on accessing the API from the
web application) and access tokens .
diff --git a/backend/resources/app/templates/error-list.tmpl b/backend/resources/app/templates/error-list.tmpl
index 73520aa36..b8af22fb8 100644
--- a/backend/resources/app/templates/error-list.tmpl
+++ b/backend/resources/app/templates/error-list.tmpl
@@ -6,13 +6,19 @@ penpot - error list
{% block content %}
- Latest error reports:
+
+
Error reports (last 200)
+
diff --git a/backend/resources/app/templates/error-report.v3.tmpl b/backend/resources/app/templates/error-report.v3.tmpl
index b36a97e49..66e2ad2ed 100644
--- a/backend/resources/app/templates/error-report.v3.tmpl
+++ b/backend/resources/app/templates/error-report.v3.tmpl
@@ -1,7 +1,7 @@
{% extends "app/templates/base.tmpl" %}
{% block title %}
-penpot - error report v2 {{id}}
+Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v3)
{% endblock %}
{% block content %}
diff --git a/backend/resources/app/templates/styles.css b/backend/resources/app/templates/styles.css
index 499f75cb3..f2c95b214 100644
--- a/backend/resources/app/templates/styles.css
+++ b/backend/resources/app/templates/styles.css
@@ -50,7 +50,13 @@ nav {
background: #e3e3e3;
}
-nav > h1 {
+nav > .title {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+}
+
+nav > .title > h1 {
padding: 0px;
margin: 0px;
font-size: 11px;
@@ -151,7 +157,6 @@ nav > div:not(:last-child) {
line-height: 18px;
min-width: 210px;
margin: 0px 20px;
- cursor: pointer;
display: flex;
border-radius: 3px;
}
diff --git a/backend/src/app/auth/oidc.clj b/backend/src/app/auth/oidc.clj
index 5330efc09..89fe77ef3 100644
--- a/backend/src/app/auth/oidc.clj
+++ b/backend/src/app/auth/oidc.clj
@@ -391,13 +391,14 @@
(defn- get-user-info
[{:keys [provider]} tdata]
(try
- (let [{:keys [kid alg] :as theader} (jwt/decode-header (:token/id tdata))]
- (when-let [key (if (str/starts-with? (name alg) "hs")
- (:client-secret provider)
- (get-in provider [:jwks kid]))]
+ (when (:token/id tdata)
+ (let [{:keys [kid alg] :as theader} (jwt/decode-header (:token/id tdata))]
+ (when-let [key (if (str/starts-with? (name alg) "hs")
+ (:client-secret provider)
+ (get-in provider [:jwks kid]))]
- (let [claims (jwt/unsign (:token/id tdata) key {:alg alg})]
- (dissoc claims :exp :iss :iat :sid :aud :sub))))
+ (let [claims (jwt/unsign (:token/id tdata) key {:alg alg})]
+ (dissoc claims :exp :iss :iat :sid :aud :sub)))))
(catch Throwable cause
(l/warn :hint "unable to get user info from JWT token (unexpected exception)"
:cause cause))))
diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj
index da9955293..e117bf7db 100644
--- a/backend/src/app/http/debug.clj
+++ b/backend/src/app/http/debug.clj
@@ -238,9 +238,9 @@
(-> (io/resource "app/templates/error-report.v2.tmpl")
(tmpl/render report)))
- (render-template-v3 [{report :content}]
+ (render-template-v3 [{report :content id :id}]
(-> (io/resource "app/templates/error-report.v3.tmpl")
- (tmpl/render report)))
+ (tmpl/render (assoc report :id id))))
]
(when-not (authorized? pool request)
diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj
index 24f9891d3..6ca7d7f68 100644
--- a/backend/src/app/http/errors.clj
+++ b/backend/src/app/http/errors.clj
@@ -74,7 +74,7 @@
::yrs/headers headers}))
(defmethod handle-exception :validation
- [err _]
+ [err request]
(let [{:keys [code] :as data} (ex-data err)]
(cond
(= code :spec-validation)
@@ -95,6 +95,11 @@
(= code :request-body-too-large)
{::yrs/status 413 ::yrs/body data}
+ (= code :invalid-image)
+ (binding [l/*context* (request->context request)]
+ (l/error :hint "unexpected error on processing image" :cause err)
+ {::yrs/status 400 ::yrs/body data})
+
:else
{::yrs/status 400 ::yrs/body data})))
diff --git a/backend/src/app/loggers/database.clj b/backend/src/app/loggers/database.clj
index fac70eac6..85e5c6e3f 100644
--- a/backend/src/app/loggers/database.clj
+++ b/backend/src/app/loggers/database.clj
@@ -56,7 +56,7 @@
:trace (ex/format-throwable cause :data? false :explain? false :header? false :summary? false)}
(when-let [params (or (:request/params context) (:params context))]
- {:params (pp/pprint-str params :width 200)})
+ {:params (pp/pprint-str params :width 200 :length 50 :level 10)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :width 200 :length 50 :level 10)})
diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj
index 9fb6515a1..9964c2824 100644
--- a/backend/src/app/media.clj
+++ b/backend/src/app/media.clj
@@ -9,7 +9,6 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
- [app.common.logging :as l]
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
@@ -227,7 +226,6 @@
(defmethod process-error org.im4java.core.InfoException
[error]
- (l/error :hint "unexpected error on processing image" :cause error)
(ex/raise :type :validation
:code :invalid-image
:hint "invalid image"
diff --git a/backend/src/app/rpc/commands/files_thumbnails.clj b/backend/src/app/rpc/commands/files_thumbnails.clj
index ceb40be9d..1db0c4ccd 100644
--- a/backend/src/app/rpc/commands/files_thumbnails.clj
+++ b/backend/src/app/rpc/commands/files_thumbnails.clj
@@ -67,6 +67,7 @@
(sv/defmethod ::get-file-object-thumbnails
"Retrieve a file object thumbnails."
{::doc/added "1.17"
+ ::doc/module :files
::sm/params [:map {:title "get-file-object-thumbnails"}
[:file-id ::sm/uuid]]
::sm/result [:map-of :string :string]
@@ -112,6 +113,7 @@
(sv/defmethod ::get-file-thumbnail
{::doc/added "1.17"
+ ::doc/module :files
::doc/deprecated "1.19"}
[{:keys [::db/pool]} {:keys [::rpc/profile-id file-id revn]}]
(dm/with-open [conn (db/open pool)]
@@ -229,11 +231,10 @@
(sv/defmethod ::get-file-data-for-thumbnail
"Retrieves the data for generate the thumbnail of the file. Used
mainly for render thumbnails on dashboard."
-
{::doc/added "1.17"
+ ::doc/module :files
::sm/params schema:get-file-data-for-thumbnail
::sm/result schema:partial-file}
-
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id features] :as props}]
(dm/with-open [conn (db/open pool)]
(files/check-read-permissions! conn profile-id file-id)
@@ -274,6 +275,7 @@
(sv/defmethod ::upsert-file-object-thumbnail
{::doc/added "1.17"
+ ::doc/module :files
::doc/deprecated "1.19"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
@@ -319,6 +321,7 @@
(sv/defmethod ::create-file-object-thumbnail
{:doc/added "1.19"
+ ::doc/module :files
::audit/skip true
::sm/params schema:create-file-object-thumbnail}
@@ -357,6 +360,7 @@
(sv/defmethod ::delete-file-object-thumbnail
{:doc/added "1.19"
+ ::doc/module :files
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id object-id]}]
@@ -395,6 +399,7 @@
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.17"
+ ::doc/module :files
::doc/deprecated "1.19"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
@@ -437,6 +442,7 @@
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.19"
+ ::doc/module :files
::audit/skip true
::sm/params [:map {:title "create-file-thumbnail"}
[:file-id ::sm/uuid]
diff --git a/backend/src/app/rpc/commands/ldap.clj b/backend/src/app/rpc/commands/ldap.clj
index e434f537c..c166f7c1a 100644
--- a/backend/src/app/rpc/commands/ldap.clj
+++ b/backend/src/app/rpc/commands/ldap.clj
@@ -38,7 +38,8 @@
"Performs the authentication using LDAP backend. Only works if LDAP
is properly configured and enabled with `login-with-ldap` flag."
{::rpc/auth false
- ::doc/added "1.15"}
+ ::doc/added "1.15"
+ ::doc/module :auth}
[{:keys [::main/props ::ldap/provider] :as cfg} params]
(when-not provider
(ex/raise :type :restriction
diff --git a/backend/src/app/rpc/commands/media.clj b/backend/src/app/rpc/commands/media.clj
index 9712f6035..93885adf3 100644
--- a/backend/src/app/rpc/commands/media.clj
+++ b/backend/src/app/rpc/commands/media.clj
@@ -171,7 +171,8 @@
:opt-un [::id ::name]))
(sv/defmethod ::create-file-media-object-from-url
- {::doc/added "1.17"}
+ {::doc/added "1.17"
+ ::doc/deprecated "1.19"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(let [cfg (update cfg ::sto/storage media/configure-assets-storage)]
(files/check-edition-permissions! pool profile-id file-id)
diff --git a/backend/src/app/rpc/commands/search.clj b/backend/src/app/rpc/commands/search.clj
index e44a21cdb..1c4026d6d 100644
--- a/backend/src/app/rpc/commands/search.clj
+++ b/backend/src/app/rpc/commands/search.clj
@@ -64,6 +64,7 @@
:opt-un [::search-term]))
(sv/defmethod ::search-files
- {::doc/added "1.17"}
+ {::doc/added "1.17"
+ ::doc/module :files}
[{:keys [::db/pool]} {:keys [::rpc/profile-id team-id search-term]}]
(some->> search-term (search-files pool profile-id team-id)))
diff --git a/backend/src/app/rpc/commands/verify_token.clj b/backend/src/app/rpc/commands/verify_token.clj
index 6f800515a..0d8ef8391 100644
--- a/backend/src/app/rpc/commands/verify_token.clj
+++ b/backend/src/app/rpc/commands/verify_token.clj
@@ -34,7 +34,8 @@
(sv/defmethod ::verify-token
{::rpc/auth false
- ::doc/added "1.15"}
+ ::doc/added "1.15"
+ ::doc/module :auth}
[{:keys [::db/pool] :as cfg} {:keys [token] :as params}]
(db/with-atomic [conn pool]
(let [claims (tokens/verify (::main/props cfg) {:token token})
diff --git a/backend/src/app/rpc/doc.clj b/backend/src/app/rpc/doc.clj
index 89d8c2280..24451e553 100644
--- a/backend/src/app/rpc/doc.clj
+++ b/backend/src/app/rpc/doc.clj
@@ -54,14 +54,14 @@
{:name (::sv/name mdata)
:module (or (some-> (::module mdata) d/name)
(-> (:ns mdata) (str/split ".") last))
- :auth (:auth mdata true)
+ :auth (::rpc/auth mdata true)
:webhook (::webhooks/event? mdata false)
:docs (::sv/docstring mdata)
:deprecated (::deprecated mdata)
:added (::added mdata)
:changes (some->> (::changes mdata) (partition-all 2) (map vec))
:spec (fmt-spec mdata)
- :entrypoint (str (cf/get :public-uri) "/api/rpc/commands/" (::sv/name mdata))
+ :entrypoint (str (cf/get :public-uri) "/api/rpc/command/" (::sv/name mdata))
:params-schema-js (fmt-schema :js mdata ::sm/params)
:result-schema-js (fmt-schema :js mdata ::sm/result)
@@ -156,7 +156,7 @@
(map (partial gen-method-doc options))
(sort-by (juxt :module :name))
(map (fn [doc]
- [(str/ffmt "/commands/%" (:name doc)) (:repr doc)]))
+ [(str/ffmt "/command/%" (:name doc)) (:repr doc)]))
(into {})))]
{:openapi "3.0.0"
:info {:version (:main cf/version)}
diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc
index 682e699b4..cb296bf4e 100644
--- a/common/src/app/common/types/shape.cljc
+++ b/common/src/app/common/types/shape.cljc
@@ -248,7 +248,7 @@
[:map
[:width :int]
[:height :int]
- [:mtype :string]
+ [:mtype {:optional true} [:maybe :string]]
[:id ::sm/uuid]]]])
(sm/def! ::path-attrs
diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs
index baa4ecefe..40c158ec0 100644
--- a/frontend/src/app/main/data/workspace/media.cljs
+++ b/frontend/src/app/main/data/workspace/media.cljs
@@ -73,16 +73,20 @@
(rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position))))))
(defn- process-uris
- [{:keys [file-id local? name uris mtype on-image on-svg]}]
+ [{:keys [file-id local? name uris mtype on-image on-svg] }]
(letfn [(svg-url? [url]
(or (and mtype (= mtype "image/svg+xml"))
(str/ends-with? url ".svg")))
- (prepare [uri]
- {:file-id file-id
- :is-local local?
- :name (or name (svg/extract-name uri))
- :url uri})
+ (upload [uri]
+ (->> (http/send! {:method :get :uri uri :mode :no-cors :response-type :blob})
+ (rx/map :body)
+ (rx/map (fn [content]
+ {:file-id file-id
+ :name (or name (svg/extract-name uri))
+ :is-local local?
+ :content content}))
+ (rx/mapcat #(rp/cmd! :upload-file-media-object %))))
(fetch-svg [name uri]
(->> (http/send! {:method :get :uri uri :mode :no-cors})
@@ -93,8 +97,7 @@
(rx/merge
(->> (rx/from uris)
(rx/filter (comp not svg-url?))
- (rx/map prepare)
- (rx/mapcat #(rp/cmd! :create-file-media-object-from-url %))
+ (rx/mapcat upload)
(rx/do on-image))
(->> (rx/from uris)
@@ -142,7 +145,7 @@
[:local? :boolean]
[:name {:optional true} :string]
[:data {:optional true} :any] ; FIXME
- [:uris {:optional true} [:vector :string]]
+ [:uris {:optional true} [:sequential :string]]
[:mtype {:optional true} :string]])
(defn- process-media-objects
@@ -213,8 +216,6 @@
:on-image #(st/emit! (dwl/add-media %)))]
(process-media-objects params)))
-;; TODO: it is really need handle SVG here, looks like it already
-;; handled separately
(defn upload-media-workspace
[{:keys [position file-id] :as params}]
(let [params (assoc params
diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
index dc70ebd6f..370285522 100644
--- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
@@ -458,8 +458,8 @@
(dnd/has-type? event "text/uri-list")
(let [data (dnd/get-data event "text/uri-list")
lines (str/lines data)
- uris (->> lines (filter #(str/starts-with? % "http")))
- data (->> lines (filter #(str/starts-with? % "data:image/")))
+ uris (filterv #(str/starts-with? % "http") lines)
+ data (filterv #(str/starts-with? % "data:image/") lines)
params {:file-id (:id file)
:position viewport-coord}
params (if (seq uris)
diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs
index 20f6e0906..a8324e7ad 100644
--- a/frontend/src/app/render.cljs
+++ b/frontend/src/app/render.cljs
@@ -151,9 +151,9 @@
(rx/tap (fn [[fonts]]
(when (seq fonts)
(st/emit! (df/fonts-fetched fonts)))))
- (rx/map (comp :objects second))))))
+ (rx/map (fn [[_ page]] {:objects (:objects page)}))))))
- objects (use-resource fetch-state)]
+ {:keys [objects]} (use-resource fetch-state)]
(when objects
(for [object-id object-ids]
diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs
index 0dcf039d6..33c242220 100644
--- a/frontend/src/app/util/import/parser.cljs
+++ b/frontend/src/app/util/import/parser.cljs
@@ -737,12 +737,13 @@
:fill-color-ref-file (get-meta fill-node :fill-color-ref-file uuid/uuid)
:fill-color-ref-id (get-meta fill-node :fill-color-ref-id uuid/uuid)
:fill-opacity (get-meta fill-node :fill-opacity d/parse-double)}))
- (mapv d/without-nils))]
+ (mapv d/without-nils)
+ (filterv #(not= (:fill-color %) "none")))]
(if (seq fills)
fills
(->> [(-> (add-fill {} node svg-data)
(d/without-nils))]
- (filterv not-empty)))))
+ (filterv #(and (not-empty %) (not= (:fill-color %) "none")))))))
(defn parse-strokes
[node svg-data]
@@ -761,12 +762,13 @@
:stroke-alignment (get-meta stroke-node :stroke-alignment keyword)
:stroke-cap-start (get-meta stroke-node :stroke-cap-start keyword)
:stroke-cap-end (get-meta stroke-node :stroke-cap-end keyword)}))
- (mapv d/without-nils))]
+ (mapv d/without-nils)
+ (filterv #(not= (:stroke-color %) "none")))]
(if (seq strokes)
strokes
(->> [(-> (add-stroke {} node svg-data)
(d/without-nils))]
- (filterv #(and (not-empty %) (not= (:stroke-style %) :none)))))))
+ (filterv #(and (not-empty %) (not= (:stroke-color %) "none") (not= (:stroke-style %) :none)))))))
(defn add-svg-content
[props node]