mirror of
https://github.com/penpot/penpot.git
synced 2025-03-12 07:41:43 -05:00
🎉 Add frame thumbnail API
This commit is contained in:
parent
fc2399a885
commit
34df52be5f
6 changed files with 182 additions and 31 deletions
|
@ -205,6 +205,9 @@
|
||||||
|
|
||||||
{:name "0065-add-trivial-spelling-fixes"
|
{:name "0065-add-trivial-spelling-fixes"
|
||||||
:fn (mg/resource "app/migrations/sql/0065-add-trivial-spelling-fixes.sql")}
|
:fn (mg/resource "app/migrations/sql/0065-add-trivial-spelling-fixes.sql")}
|
||||||
|
|
||||||
|
{:name "0066-add-frame-thumbnail-table"
|
||||||
|
:fn (mg/resource "app/migrations/sql/0066-add-frame-thumbnail-table.sql")}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
CREATE TABLE file_frame_thumbnail (
|
||||||
|
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
|
||||||
|
frame_id uuid NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||||
|
|
||||||
|
data text NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY(file_id, frame_id)
|
||||||
|
);
|
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
;; --- Helpers & Specs
|
;; --- Helpers & Specs
|
||||||
|
|
||||||
|
(s/def ::frame-id ::us/uuid)
|
||||||
|
(s/def ::file-id ::us/uuid)
|
||||||
(s/def ::id ::us/uuid)
|
(s/def ::id ::us/uuid)
|
||||||
(s/def ::name ::us/string)
|
(s/def ::name ::us/string)
|
||||||
(s/def ::profile-id ::us/uuid)
|
(s/def ::profile-id ::us/uuid)
|
||||||
|
@ -472,3 +474,25 @@
|
||||||
{:id id})))
|
{:id id})))
|
||||||
|
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Mutation: Upsert frame thumbnail
|
||||||
|
|
||||||
|
(def sql:upsert-frame-thumbnail
|
||||||
|
"insert into file_frame_thumbnail(file_id, frame_id, data)
|
||||||
|
values (?, ?, ?)
|
||||||
|
on conflict(file_id, frame_id) do
|
||||||
|
update set data = ?;")
|
||||||
|
|
||||||
|
(s/def ::data ::us/string)
|
||||||
|
(s/def ::upsert-frame-thumbnail
|
||||||
|
(s/keys :req-un [::profile-id ::file-id ::frame-id ::data]))
|
||||||
|
|
||||||
|
(sv/defmethod ::upsert-frame-thumbnail
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id frame-id data]}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(files/check-edition-permissions! conn profile-id file-id)
|
||||||
|
(db/exec-one! conn [sql:upsert-frame-thumbnail file-id frame-id data data])
|
||||||
|
nil))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
;; --- Helpers & Specs
|
;; --- Helpers & Specs
|
||||||
|
|
||||||
|
(s/def ::frame-id ::us/uuid)
|
||||||
(s/def ::id ::us/uuid)
|
(s/def ::id ::us/uuid)
|
||||||
(s/def ::name ::us/string)
|
(s/def ::name ::us/string)
|
||||||
(s/def ::project-id ::us/uuid)
|
(s/def ::project-id ::us/uuid)
|
||||||
|
@ -392,6 +393,7 @@
|
||||||
)
|
)
|
||||||
select * from recent_files where row_num <= 10;")
|
select * from recent_files where row_num <= 10;")
|
||||||
|
|
||||||
|
|
||||||
(s/def ::team-recent-files
|
(s/def ::team-recent-files
|
||||||
(s/keys :req-un [::profile-id ::team-id]))
|
(s/keys :req-un [::profile-id ::team-id]))
|
||||||
|
|
||||||
|
@ -401,6 +403,25 @@
|
||||||
(teams/check-read-permissions! conn profile-id team-id)
|
(teams/check-read-permissions! conn profile-id team-id)
|
||||||
(db/exec! conn [sql:team-recent-files team-id])))
|
(db/exec! conn [sql:team-recent-files team-id])))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- QUERY: get the thumbnail for an frame
|
||||||
|
|
||||||
|
(def ^:private sql:file-frame-thumbnail
|
||||||
|
"select data
|
||||||
|
from file_frame_thumbnail
|
||||||
|
where file_id = ?
|
||||||
|
and frame_id = ?")
|
||||||
|
|
||||||
|
(s/def ::file-frame-thumbnail
|
||||||
|
(s/keys :req-un [::profile-id ::file-id ::frame-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::file-frame-thumbnail
|
||||||
|
[{:keys [pool]} {:keys [profile-id file-id frame-id]}]
|
||||||
|
(with-open [conn (db/open pool)]
|
||||||
|
(check-read-permissions! conn profile-id file-id)
|
||||||
|
(db/exec-one! conn [sql:file-frame-thumbnail file-id frame-id])))
|
||||||
|
|
||||||
|
|
||||||
;; --- Helpers
|
;; --- Helpers
|
||||||
|
|
||||||
(defn decode-row
|
(defn decode-row
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
after some period of inactivity (the default threshold is 72h)."
|
after some period of inactivity (the default threshold is 72h)."
|
||||||
(:require
|
(:require
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.pages.migrations :as pmg]
|
[app.common.pages.migrations :as pmg]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
limit 10
|
limit 10
|
||||||
for update skip locked")
|
for update skip locked")
|
||||||
|
|
||||||
|
|
||||||
(defn- retrieve-candidates
|
(defn- retrieve-candidates
|
||||||
[{:keys [conn max-age] :as cfg}]
|
[{:keys [conn max-age] :as cfg}]
|
||||||
(let [interval (db/interval max-age)]
|
(let [interval (db/interval max-age)]
|
||||||
|
@ -64,12 +66,11 @@
|
||||||
(comp
|
(comp
|
||||||
(map :objects)
|
(map :objects)
|
||||||
(mapcat vals)
|
(mapcat vals)
|
||||||
(map (fn [{:keys [type] :as obj}]
|
(keep (fn [{:keys [type] :as obj}]
|
||||||
(case type
|
(case type
|
||||||
:path (get-in obj [:fill-image :id])
|
:path (get-in obj [:fill-image :id])
|
||||||
:image (get-in obj [:metadata :id])
|
:image (get-in obj [:metadata :id])
|
||||||
nil)))
|
nil)))))
|
||||||
(filter uuid?)))
|
|
||||||
|
|
||||||
(defn- collect-used-media
|
(defn- collect-used-media
|
||||||
[data]
|
[data]
|
||||||
|
@ -80,13 +81,28 @@
|
||||||
(into collect-media-xf pages)
|
(into collect-media-xf pages)
|
||||||
(into (keys (:media data))))))
|
(into (keys (:media data))))))
|
||||||
|
|
||||||
|
(def ^:private
|
||||||
|
collect-frames-xf
|
||||||
|
(comp
|
||||||
|
(map :objects)
|
||||||
|
(mapcat vals)
|
||||||
|
(filter cph/frame-shape?)
|
||||||
|
(keep :id)))
|
||||||
|
|
||||||
|
(defn- collect-frames
|
||||||
|
[data]
|
||||||
|
(let [pages (concat
|
||||||
|
(vals (:pages-index data))
|
||||||
|
(vals (:components data)))]
|
||||||
|
(into #{} collect-frames-xf pages)))
|
||||||
|
|
||||||
(defn- process-file
|
(defn- process-file
|
||||||
[{:keys [conn] :as cfg} {:keys [id data age] :as file}]
|
[{:keys [conn] :as cfg} {:keys [id data age] :as file}]
|
||||||
(let [data (-> (blob/decode data)
|
(let [data (-> (blob/decode data)
|
||||||
(assoc :id id)
|
(assoc :id id)
|
||||||
(pmg/migrate-data))
|
(pmg/migrate-data))]
|
||||||
|
|
||||||
used (collect-used-media data)
|
(let [used (collect-used-media data)
|
||||||
unused (->> (db/query conn :file-media-object {:file-id id})
|
unused (->> (db/query conn :file-media-object {:file-id id})
|
||||||
(remove #(contains? used (:id %))))]
|
(remove #(contains? used (:id %))))]
|
||||||
|
|
||||||
|
@ -111,6 +127,13 @@
|
||||||
;; objects. The touch mechanism is needed because many files can
|
;; objects. The touch mechanism is needed because many files can
|
||||||
;; point to the same storage objects and we can't just delete
|
;; point to the same storage objects and we can't just delete
|
||||||
;; them.
|
;; them.
|
||||||
(db/delete! conn :file-media-object {:id (:id mobj)}))
|
(db/delete! conn :file-media-object {:id (:id mobj)})))
|
||||||
|
|
||||||
|
(let [sql (str "delete from file_frame_thumbnail "
|
||||||
|
" where file_id = ? and not (frame_id = ANY(?))")
|
||||||
|
ids (->> (collect-frames data)
|
||||||
|
(db/create-array conn "uuid"))]
|
||||||
|
;; delete the unused frame thumbnails
|
||||||
|
(db/exec! conn [sql (:id file) ids]))
|
||||||
|
|
||||||
nil))
|
nil))
|
||||||
|
|
|
@ -389,3 +389,73 @@
|
||||||
(t/is (th/ex-info? error))
|
(t/is (th/ex-info? error))
|
||||||
(t/is (= (:type error-data) :not-found))))
|
(t/is (= (:type error-data) :not-found))))
|
||||||
))
|
))
|
||||||
|
|
||||||
|
(t/deftest query-frame-thumbnails
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
file (th/create-file* 1 {:profile-id (:id prof)
|
||||||
|
:project-id (:default-project-id prof)
|
||||||
|
:is-shared false})
|
||||||
|
data {::th/type :file-frame-thumbnail
|
||||||
|
:profile-id (:id prof)
|
||||||
|
:file-id (:id file)
|
||||||
|
:frame-id (uuid/next)}]
|
||||||
|
|
||||||
|
;;insert an entry on the database with a test value for the thumbnail of this frame
|
||||||
|
(db/exec-one! th/*pool*
|
||||||
|
["insert into file_frame_thumbnail(file_id, frame_id, data) values (?, ?, ?)"
|
||||||
|
(:file-id data) (:frame-id data) "testvalue"])
|
||||||
|
|
||||||
|
(let [out (th/query! data)]
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(let [result (:result out)]
|
||||||
|
(t/is (= 1 (count result)))
|
||||||
|
(t/is (= "testvalue" (:data result)))))))
|
||||||
|
|
||||||
|
(t/deftest insert-frame-thumbnails
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
file (th/create-file* 1 {:profile-id (:id prof)
|
||||||
|
:project-id (:default-project-id prof)
|
||||||
|
:is-shared false})
|
||||||
|
data {::th/type :upsert-frame-thumbnail
|
||||||
|
:profile-id (:id prof)
|
||||||
|
:file-id (:id file)
|
||||||
|
:frame-id (uuid/next)
|
||||||
|
:data "test insert new value"}
|
||||||
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(t/is (nil? (:result out)))
|
||||||
|
|
||||||
|
;;retrieve the value from the database and check its content
|
||||||
|
(let [result (db/exec-one!
|
||||||
|
th/*pool*
|
||||||
|
["select data from file_frame_thumbnail where file_id = ? and frame_id = ?"
|
||||||
|
(:file-id data) (:frame-id data)])]
|
||||||
|
(t/is (= "test insert new value" (:data result))))))
|
||||||
|
|
||||||
|
(t/deftest frame-thumbnails
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
file (th/create-file* 1 {:profile-id (:id prof)
|
||||||
|
:project-id (:default-project-id prof)
|
||||||
|
:is-shared false})
|
||||||
|
data {::th/type :upsert-frame-thumbnail
|
||||||
|
:profile-id (:id prof)
|
||||||
|
:file-id (:id file)
|
||||||
|
:frame-id (uuid/next)
|
||||||
|
:data "updated value"}]
|
||||||
|
|
||||||
|
;;insert an entry on the database with and old value for the thumbnail of this frame
|
||||||
|
(db/exec-one! th/*pool*
|
||||||
|
["insert into file_frame_thumbnail(file_id, frame_id, data) values (?, ?, ?)"
|
||||||
|
(:file-id data) (:frame-id data) "old value"])
|
||||||
|
|
||||||
|
(let [out (th/mutation! data)]
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(t/is (nil? (:result out)))
|
||||||
|
|
||||||
|
;;retrieve the value from the database and check its content
|
||||||
|
(let [result (db/exec-one!
|
||||||
|
th/*pool*
|
||||||
|
["select data from file_frame_thumbnail where file_id = ? and frame_id = ?"
|
||||||
|
(:file-id data) (:frame-id data)])]
|
||||||
|
(t/is (= "updated value" (:data result)))))))
|
||||||
|
|
Loading…
Add table
Reference in a new issue