mirror of
https://github.com/penpot/penpot.git
synced 2025-02-12 18:18:24 -05:00
🎉 Add backend code for share token handling.
This commit is contained in:
parent
9492fe188d
commit
4105692dee
6 changed files with 304 additions and 157 deletions
|
@ -124,6 +124,8 @@ CREATE TABLE page (
|
|||
version bigint NOT NULL DEFAULT 0,
|
||||
revn bigint NOT NULL DEFAULT 0,
|
||||
|
||||
share_token text NULL DEFAULT NULL,
|
||||
|
||||
ordering smallint NOT NULL,
|
||||
|
||||
name text NOT NULL,
|
||||
|
@ -133,6 +135,10 @@ CREATE TABLE page (
|
|||
CREATE INDEX page__file_id__idx
|
||||
ON page(file_id);
|
||||
|
||||
ALTER TABLE page
|
||||
ALTER COLUMN data SET STORAGE EXTERNAL,
|
||||
ALTER COLUMN share_token SET STORAGE PLAIN;
|
||||
|
||||
CREATE FUNCTION handle_page_update()
|
||||
RETURNS TRIGGER AS $pagechange$
|
||||
DECLARE
|
||||
|
|
|
@ -106,6 +106,42 @@
|
|||
|
||||
|
||||
|
||||
;; --- Mutation: Generate Share Token
|
||||
|
||||
(declare assign-page-share-token)
|
||||
|
||||
(s/def ::generate-page-share-token
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(sm/defmutation ::generate-page-share-token
|
||||
[{:keys [id] :as params}]
|
||||
(let [token (-> (sodi.prng/random-bytes 16)
|
||||
(sodi.util/bytes->b64s))]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(assign-page-share-token conn id token))))
|
||||
|
||||
(def ^:private sql:update-page-share-token
|
||||
"update page set share_token = $2 where id = $1")
|
||||
|
||||
(defn- assign-page-share-token
|
||||
[conn id token]
|
||||
(-> (db/query-one conn [sql:update-page-share-token id token])
|
||||
(p/then (fn [_] {:id id :share-token token}))))
|
||||
|
||||
|
||||
|
||||
;; --- Mutation: Clear Share Token
|
||||
|
||||
(s/def ::clear-page-share-token
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(sm/defmutation ::clear-page-share-token
|
||||
[{:keys [id] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(assign-page-share-token conn id nil)))
|
||||
|
||||
|
||||
|
||||
;; --- Mutation: Update Page
|
||||
|
||||
;; A generic, Changes based (granular) page update method.
|
||||
|
|
|
@ -44,49 +44,24 @@
|
|||
[conn id]
|
||||
(db/query-one conn [sql:project id]))
|
||||
|
||||
(s/def ::viewer-bundle-by-page-id
|
||||
(s/keys :req-un [::profile-id ::page-id]))
|
||||
(s/def ::share-token ::us/string)
|
||||
(s/def ::viewer-bundle
|
||||
(s/keys :req-un [::page-id]
|
||||
:opt-un [::profile-id ::share-token]))
|
||||
|
||||
(sq/defquery ::viewer-bundle-by-page-id
|
||||
[{:keys [profile-id page-id]}]
|
||||
(sq/defquery ::viewer-bundle
|
||||
[{:keys [profile-id page-id share-token] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/let [page (pages/retrieve-page conn page-id)
|
||||
file (files/retrieve-file conn (:file-id page))
|
||||
images (files/retrieve-file-images conn page)
|
||||
project (retrieve-project conn (:project-id file))]
|
||||
(files/check-edition-permissions! conn profile-id (:file-id page))
|
||||
(if (string? share-token)
|
||||
(when (not= share-token (:share-token page))
|
||||
(ex/raise :type :validation
|
||||
:code :not-authorized))
|
||||
(files/check-edition-permissions! conn profile-id (:file-id page)))
|
||||
{:page page
|
||||
:file file
|
||||
:images images
|
||||
:project project})))
|
||||
|
||||
|
||||
;; --- Query: Viewer Bundle (By Share ID)
|
||||
|
||||
(declare retrieve-page-by-share-id)
|
||||
|
||||
(s/def ::viewer-bundle-by-share-id
|
||||
(s/keys :req-un [::share-id]
|
||||
:opt-un [::profile-id]))
|
||||
|
||||
(sq/defquery ::viewer-bundle-by-share-id
|
||||
[{:keys [share-id]}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/let [page (retrieve-page-by-share-id conn share-id)
|
||||
file (files/retrieve-file conn (:file-id page))
|
||||
images (files/retrieve-file-images conn page)
|
||||
project (retrieve-project conn (:project-id file))]
|
||||
{:page page
|
||||
:file file
|
||||
:images images
|
||||
:project project})))
|
||||
|
||||
(def ^:private sql:page-by-share-id
|
||||
"select p.* from page as p where share_id=$1")
|
||||
|
||||
(defn- retrieve-page-by-share-id
|
||||
[conn share-id]
|
||||
(-> (db/query-one conn [sql:page-by-share-id share-id])
|
||||
(p/then' su/raise-not-found-if-nil)
|
||||
(p/then' pages/decode-row)))
|
||||
|
||||
|
|
|
@ -13,151 +13,167 @@
|
|||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.tests.helpers :as th]))
|
||||
|
||||
(t/deftest process-change-add-shape
|
||||
(t/deftest process-change-add-obj-1
|
||||
(let [data cp/default-page-data
|
||||
id (uuid/next)
|
||||
chg {:type :add-shape
|
||||
chg {:type :add-obj
|
||||
:id id
|
||||
:session-id (uuid/next)
|
||||
:shape {:id id
|
||||
:type :rect
|
||||
:name "rect"}}
|
||||
:frame-id uuid/zero
|
||||
:obj {:id id
|
||||
:frame-id uuid/zero
|
||||
:type :rect
|
||||
:name "rect"}}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
(t/is (= 1 (count (:shapes res))))
|
||||
(t/is (= 0 (count (:canvas res))))
|
||||
(t/is (= 2 (count (:objects res))))
|
||||
(t/is (= (:obj chg) (get-in res [:objects id])))
|
||||
(t/is (= [id] (get-in res [:objects uuid/zero :shapes])))))
|
||||
|
||||
(t/is (= id (get-in res [:shapes 0])))
|
||||
(t/is (= (:shape chg)
|
||||
(get-in res [:shapes-by-id id])))))
|
||||
|
||||
(t/deftest process-change-add-canvas
|
||||
(t/deftest process-change-mod-obj
|
||||
(let [data cp/default-page-data
|
||||
id (uuid/next)
|
||||
chg {:type :add-canvas
|
||||
:id id
|
||||
:session-id (uuid/next)
|
||||
:shape {:id id
|
||||
:type :rect
|
||||
:name "rect"}}
|
||||
chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :set
|
||||
:attr :name
|
||||
:val "foobar"}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
(t/is (= 0 (count (:shapes res))))
|
||||
(t/is (= 1 (count (:canvas res))))
|
||||
|
||||
(t/is (= id (get-in res [:canvas 0])))
|
||||
(t/is (= (:shape chg)
|
||||
(get-in res [:shapes-by-id id])))))
|
||||
(t/is (= "foobar" (get-in res [:objects uuid/zero :name])))))
|
||||
|
||||
|
||||
(t/deftest process-change-mod-shape
|
||||
(t/deftest process-change-del-obj-1
|
||||
(let [id (uuid/next)
|
||||
data (merge cp/default-page-data
|
||||
{:shapes [id]
|
||||
:shapes-by-id {id {:id id
|
||||
:type :rect
|
||||
:name "rect"}}})
|
||||
|
||||
chg {:type :mod-shape
|
||||
:id id
|
||||
:session-id (uuid/next)
|
||||
:operations [[:set :name "foobar"]]}
|
||||
data (-> cp/default-page-data
|
||||
(assoc-in [:objects uuid/zero :shapes] [id])
|
||||
(assoc-in [:objects id] {:id id
|
||||
:frame-id uuid/zero
|
||||
:type :rect
|
||||
:name "rect"}))
|
||||
chg {:type :del-obj
|
||||
:id id}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
(t/is (= 1 (count (:shapes res))))
|
||||
(t/is (= 0 (count (:canvas res))))
|
||||
(t/is (= "foobar"
|
||||
(get-in res [:shapes-by-id id :name])))))
|
||||
(t/is (= 1 (count (:objects res))))
|
||||
(t/is (= [] (get-in res [:objects uuid/zero :shapes])))))
|
||||
|
||||
(t/deftest process-change-mod-opts
|
||||
(t/testing "mod-opts add"
|
||||
(let [data cp/default-page-data
|
||||
chg {:type :mod-opts
|
||||
:session-id (uuid/next)
|
||||
:operations [[:set :foo "bar"]]}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
(t/is (= 0 (count (:shapes res))))
|
||||
(t/is (= 0 (count (:canvas res))))
|
||||
(t/is (empty? (:shapes-by-id res)))
|
||||
(t/is (= "bar" (get-in res [:options :foo])))))
|
||||
|
||||
(t/testing "mod-opts set nil"
|
||||
(let [data (merge cp/default-page-data
|
||||
{:options {:foo "bar"}})
|
||||
chg {:type :mod-opts
|
||||
:session-id (uuid/next)
|
||||
:operations [[:set :foo nil]]}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
(t/is (= 0 (count (:shapes res))))
|
||||
(t/is (= 0 (count (:canvas res))))
|
||||
(t/is (empty? (:shapes-by-id res)))
|
||||
(t/is (not (contains? (:options res) :foo)))))
|
||||
)
|
||||
|
||||
|
||||
(t/deftest process-change-del-shape
|
||||
(t/deftest process-change-del-obj-2
|
||||
(let [id (uuid/next)
|
||||
data (merge cp/default-page-data
|
||||
{:shapes [id]
|
||||
:shapes-by-id {id {:id id
|
||||
:type :rect
|
||||
:name "rect"}}})
|
||||
chg {:type :del-shape
|
||||
:id id
|
||||
:session-id (uuid/next)}
|
||||
data (-> cp/default-page-data
|
||||
(assoc-in [:objects uuid/zero :shapes] [id])
|
||||
(assoc-in [:objects id] {:id id
|
||||
:frame-id uuid/zero
|
||||
:type :rect
|
||||
:name "rect"}))
|
||||
chg {:type :del-obj
|
||||
:id uuid/zero}
|
||||
res (cp/process-changes data [chg])]
|
||||
(t/is (= 0 (count (:objects res))))))
|
||||
|
||||
(t/is (= 0 (count (:shapes res))))
|
||||
(t/is (= 0 (count (:canvas res))))
|
||||
(t/is (empty? (:shapes-by-id res)))))
|
||||
|
||||
(t/deftest process-change-del-canvas
|
||||
(let [id (uuid/next)
|
||||
data (merge cp/default-page-data
|
||||
{:canvas [id]
|
||||
:shapes-by-id {id {:id id
|
||||
:type :canvas
|
||||
:name "rect"}}})
|
||||
chg {:type :del-canvas
|
||||
:id id
|
||||
:session-id (uuid/next)}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
(t/is (= 0 (count (:shapes res))))
|
||||
(t/is (= 0 (count (:canvas res))))
|
||||
(t/is (empty? (:shapes-by-id res)))))
|
||||
|
||||
|
||||
(t/deftest process-change-mov-shape
|
||||
(t/deftest process-change-mod-obj-abs-order
|
||||
(let [id1 (uuid/next)
|
||||
id2 (uuid/next)
|
||||
id3 (uuid/next)
|
||||
data (merge cp/default-page-data
|
||||
{:shapes [id1 id2 id3]})]
|
||||
data (-> cp/default-page-data
|
||||
(assoc-in [:objects uuid/zero :shapes] [id1 id2 id3]))]
|
||||
|
||||
(t/testing "mov-canvas 1"
|
||||
(let [chg {:type :mov-shape
|
||||
:id id3
|
||||
:index 0
|
||||
:session-id (uuid/next)}
|
||||
(t/testing "abs order 1"
|
||||
(let [chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :abs-order
|
||||
:id id3
|
||||
:index 0}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
(t/is (= [id3 id1 id2] (:shapes res)))))
|
||||
|
||||
(t/testing "mov-canvas 2"
|
||||
(let [chg {:type :mov-shape
|
||||
:id id3
|
||||
:index 100
|
||||
:session-id (uuid/next)}
|
||||
res (cp/process-changes data [chg])]
|
||||
(t/is (= [id1 id2 id3] (:shapes res)))))
|
||||
;; (clojure.pprint/pprint data)
|
||||
;; (clojure.pprint/pprint res)
|
||||
|
||||
(t/testing "mov-canvas 3"
|
||||
(let [chg {:type :mov-shape
|
||||
:id id3
|
||||
:index 1
|
||||
:session-id (uuid/next)}
|
||||
(t/is (= [id3 id1 id2] (get-in res [:objects uuid/zero :shapes])))))
|
||||
|
||||
(t/testing "abs order 2"
|
||||
(let [chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :abs-order
|
||||
:id id1
|
||||
:index 100}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
(t/is (= [id1 id3 id2] (:shapes res)))))
|
||||
|
||||
;; (clojure.pprint/pprint data)
|
||||
;; (clojure.pprint/pprint res)
|
||||
|
||||
(t/is (= [id2 id3 id1] (get-in res [:objects uuid/zero :shapes])))))
|
||||
|
||||
(t/testing "abs order 3"
|
||||
(let [chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :abs-order
|
||||
:id id3
|
||||
:index 1}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
;; (clojure.pprint/pprint data)
|
||||
;; (clojure.pprint/pprint res)
|
||||
|
||||
(t/is (= [id1 id3 id2] (get-in res [:objects uuid/zero :shapes])))))
|
||||
))
|
||||
|
||||
|
||||
(t/deftest process-change-mod-obj-rel-order
|
||||
(let [id1 (uuid/next)
|
||||
id2 (uuid/next)
|
||||
id3 (uuid/next)
|
||||
data (-> cp/default-page-data
|
||||
(assoc-in [:objects uuid/zero :shapes] [id1 id2 id3]))]
|
||||
|
||||
(t/testing "rel order 1"
|
||||
(let [chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :rel-order
|
||||
:id id3
|
||||
:loc :down}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
;; (clojure.pprint/pprint data)
|
||||
;; (clojure.pprint/pprint res)
|
||||
|
||||
(t/is (= [id1 id3 id2] (get-in res [:objects uuid/zero :shapes])))))
|
||||
|
||||
|
||||
(t/testing "rel order 2"
|
||||
(let [chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :rel-order
|
||||
:id id1
|
||||
:loc :top}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
;; (clojure.pprint/pprint data)
|
||||
;; (clojure.pprint/pprint res)
|
||||
|
||||
(t/is (= [id2 id3 id1] (get-in res [:objects uuid/zero :shapes])))))
|
||||
|
||||
(t/testing "rel order 3"
|
||||
(let [chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :rel-order
|
||||
:id id2
|
||||
:loc :up}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
;; (clojure.pprint/pprint data)
|
||||
;; (clojure.pprint/pprint res)
|
||||
|
||||
(t/is (= [id1 id3 id2] (get-in res [:objects uuid/zero :shapes])))))
|
||||
|
||||
(t/testing "rel order 4"
|
||||
(let [chg {:type :mod-obj
|
||||
:id uuid/zero
|
||||
:operations [{:type :rel-order
|
||||
:id id3
|
||||
:loc :bottom}]}
|
||||
res (cp/process-changes data [chg])]
|
||||
|
||||
;; (clojure.pprint/pprint data)
|
||||
;; (clojure.pprint/pprint res)
|
||||
|
||||
(t/is (= [id3 id1 id2] (get-in res [:objects uuid/zero :shapes])))))
|
||||
))
|
||||
|
||||
|
|
|
@ -39,9 +39,20 @@
|
|||
(t/is (= (:id data) (:id result)))
|
||||
(t/is (= (:name data) (:name result)))
|
||||
(t/is (= (:data data) (:data result)))
|
||||
(t/is (nil? (:share-token result)))
|
||||
(t/is (= 0 (:version result)))
|
||||
(t/is (= 0 (:revn result))))))
|
||||
|
||||
(t/testing "generate share token"
|
||||
(let [data {::sm/type :generate-page-share-token
|
||||
:id page-id}
|
||||
out (th/try-on! (sm/handle data))]
|
||||
|
||||
(th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (string? (:share-token result))))))
|
||||
|
||||
(t/testing "query pages"
|
||||
(let [data {::sq/type :pages
|
||||
:file-id (:id file)
|
||||
|
@ -55,6 +66,7 @@
|
|||
(t/is (= 1 (count result)))
|
||||
(t/is (= page-id (get-in result [0 :id])))
|
||||
(t/is (= "test page" (get-in result [0 :name])))
|
||||
(t/is (string? (get-in result [0 :share-token])))
|
||||
(t/is (:id file) (get-in result [0 :file-id])))))
|
||||
|
||||
(t/testing "delete page"
|
||||
|
|
102
backend/tests/uxbox/tests/test_services_viewer.clj
Normal file
102
backend/tests/uxbox/tests/test_services_viewer.clj
Normal file
|
@ -0,0 +1,102 @@
|
|||
(ns uxbox.tests.test-services-viewer
|
||||
(:require
|
||||
[clojure.test :as t]
|
||||
[promesa.core :as p]
|
||||
[datoteka.core :as fs]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.media :as media]
|
||||
[uxbox.core :refer [system]]
|
||||
[uxbox.http :as http]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.queries :as sq]
|
||||
[uxbox.tests.helpers :as th]
|
||||
[uxbox.util.storage :as ust]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[vertx.util :as vu]))
|
||||
|
||||
(t/use-fixtures :once th/state-init)
|
||||
(t/use-fixtures :each th/database-reset)
|
||||
|
||||
(t/deftest retrieve-bundle
|
||||
(let [prof @(th/create-profile db/pool 1)
|
||||
prof2 @(th/create-profile db/pool 2)
|
||||
team (:default-team prof)
|
||||
proj (:default-project prof)
|
||||
|
||||
file @(th/create-file db/pool (:id prof) (:id proj) 1)
|
||||
page @(th/create-page db/pool (:id prof) (:id file) 1)
|
||||
token (atom nil)]
|
||||
|
||||
|
||||
(t/testing "authenticated with page-id"
|
||||
(let [data {::sq/type :viewer-bundle
|
||||
:profile-id (:id prof)
|
||||
:page-id (:id page)}
|
||||
|
||||
out (th/try-on! (sq/handle data))]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
|
||||
(let [result (:result out)]
|
||||
(t/is (contains? result :page))
|
||||
(t/is (contains? result :file))
|
||||
(t/is (contains? result :project)))))
|
||||
|
||||
(t/testing "generate share token"
|
||||
(let [data {::sm/type :generate-page-share-token
|
||||
:id (:id page)}
|
||||
out (th/try-on! (sm/handle data))]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(t/is (nil? (:error out)))
|
||||
(let [result (:result out)]
|
||||
(t/is (string? (:share-token result)))
|
||||
(reset! token (:share-token result)))))
|
||||
|
||||
(t/testing "authenticated with page-id"
|
||||
(let [data {::sq/type :viewer-bundle
|
||||
:profile-id (:id prof2)
|
||||
:page-id (:id page)}
|
||||
out (th/try-on! (sq/handle data))]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
||||
(let [error (:error out)
|
||||
error-data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type error-data) :service-error))
|
||||
(t/is (= (:name error-data) :uxbox.services.queries.viewer/viewer-bundle)))
|
||||
|
||||
(let [error (ex-cause (:error out))
|
||||
error-data (ex-data error)]
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= (:type error-data) :not-found)))))
|
||||
|
||||
(t/testing "authenticated with page-id and token"
|
||||
(let [data {::sq/type :viewer-bundle
|
||||
:profile-id (:id prof2)
|
||||
:page-id (:id page)
|
||||
:share-token @token}
|
||||
out (th/try-on! (sq/handle data))]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
||||
(let [result (:result out)]
|
||||
(t/is (contains? result :page))
|
||||
(t/is (contains? result :file))
|
||||
(t/is (contains? result :project)))))
|
||||
|
||||
(t/testing "not authenticated with page-id and token"
|
||||
(let [data {::sq/type :viewer-bundle
|
||||
:page-id (:id page)
|
||||
:share-token @token}
|
||||
out (th/try-on! (sq/handle data))]
|
||||
|
||||
;; (th/print-result! out)
|
||||
|
||||
(let [result (:result out)]
|
||||
(t/is (contains? result :page))
|
||||
(t/is (contains? result :file))
|
||||
(t/is (contains? result :project)))))
|
||||
))
|
Loading…
Add table
Reference in a new issue