0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 02:28:18 -05:00

🎉 Add backend code for share token handling.

This commit is contained in:
Andrey Antukh 2020-04-07 14:20:29 +02:00 committed by Alonso Torres
parent 9492fe188d
commit 4105692dee
6 changed files with 304 additions and 157 deletions

View file

@ -124,6 +124,8 @@ CREATE TABLE page (
version bigint NOT NULL DEFAULT 0, version bigint NOT NULL DEFAULT 0,
revn bigint NOT NULL DEFAULT 0, revn bigint NOT NULL DEFAULT 0,
share_token text NULL DEFAULT NULL,
ordering smallint NOT NULL, ordering smallint NOT NULL,
name text NOT NULL, name text NOT NULL,
@ -133,6 +135,10 @@ CREATE TABLE page (
CREATE INDEX page__file_id__idx CREATE INDEX page__file_id__idx
ON page(file_id); 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() CREATE FUNCTION handle_page_update()
RETURNS TRIGGER AS $pagechange$ RETURNS TRIGGER AS $pagechange$
DECLARE DECLARE

View file

@ -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 ;; --- Mutation: Update Page
;; A generic, Changes based (granular) page update method. ;; A generic, Changes based (granular) page update method.

View file

@ -44,49 +44,24 @@
[conn id] [conn id]
(db/query-one conn [sql:project id])) (db/query-one conn [sql:project id]))
(s/def ::viewer-bundle-by-page-id (s/def ::share-token ::us/string)
(s/keys :req-un [::profile-id ::page-id])) (s/def ::viewer-bundle
(s/keys :req-un [::page-id]
:opt-un [::profile-id ::share-token]))
(sq/defquery ::viewer-bundle-by-page-id (sq/defquery ::viewer-bundle
[{:keys [profile-id page-id]}] [{:keys [profile-id page-id share-token] :as params}]
(db/with-atomic [conn db/pool] (db/with-atomic [conn db/pool]
(p/let [page (pages/retrieve-page conn page-id) (p/let [page (pages/retrieve-page conn page-id)
file (files/retrieve-file conn (:file-id page)) file (files/retrieve-file conn (:file-id page))
images (files/retrieve-file-images conn page) images (files/retrieve-file-images conn page)
project (retrieve-project conn (:project-id file))] 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 {:page page
:file file :file file
:images images :images images
:project project}))) :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)))

View file

@ -13,151 +13,167 @@
[uxbox.util.uuid :as uuid] [uxbox.util.uuid :as uuid]
[uxbox.tests.helpers :as th])) [uxbox.tests.helpers :as th]))
(t/deftest process-change-add-shape (t/deftest process-change-add-obj-1
(let [data cp/default-page-data (let [data cp/default-page-data
id (uuid/next) id (uuid/next)
chg {:type :add-shape chg {:type :add-obj
:id id :id id
:session-id (uuid/next) :frame-id uuid/zero
:shape {:id id :obj {:id id
:frame-id uuid/zero
:type :rect :type :rect
:name "rect"}} :name "rect"}}
res (cp/process-changes data [chg])] res (cp/process-changes data [chg])]
(t/is (= 1 (count (:shapes res)))) (t/is (= 2 (count (:objects res))))
(t/is (= 0 (count (:canvas 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/deftest process-change-mod-obj
(t/is (= (:shape chg)
(get-in res [:shapes-by-id id])))))
(t/deftest process-change-add-canvas
(let [data cp/default-page-data (let [data cp/default-page-data
id (uuid/next) chg {:type :mod-obj
chg {:type :add-canvas :id uuid/zero
:id id :operations [{:type :set
:session-id (uuid/next) :attr :name
:shape {:id id :val "foobar"}]}
:type :rect
:name "rect"}}
res (cp/process-changes data [chg])] res (cp/process-changes data [chg])]
(t/is (= 0 (count (:shapes res)))) (t/is (= "foobar" (get-in res [:objects uuid/zero :name])))))
(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/deftest process-change-mod-shape (t/deftest process-change-del-obj-1
(let [id (uuid/next) (let [id (uuid/next)
data (merge cp/default-page-data data (-> cp/default-page-data
{:shapes [id] (assoc-in [:objects uuid/zero :shapes] [id])
:shapes-by-id {id {:id id (assoc-in [:objects id] {:id id
:frame-id uuid/zero
:type :rect :type :rect
:name "rect"}}}) :name "rect"}))
chg {:type :del-obj
chg {:type :mod-shape :id id}
:id id
:session-id (uuid/next)
:operations [[:set :name "foobar"]]}
res (cp/process-changes data [chg])] res (cp/process-changes data [chg])]
(t/is (= 1 (count (:shapes res)))) (t/is (= 1 (count (:objects res))))
(t/is (= 0 (count (:canvas res)))) (t/is (= [] (get-in res [:objects uuid/zero :shapes])))))
(t/is (= "foobar"
(get-in res [:shapes-by-id id :name])))))
(t/deftest process-change-mod-opts (t/deftest process-change-del-obj-2
(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
(let [id (uuid/next) (let [id (uuid/next)
data (merge cp/default-page-data data (-> cp/default-page-data
{:shapes [id] (assoc-in [:objects uuid/zero :shapes] [id])
:shapes-by-id {id {:id id (assoc-in [:objects id] {:id id
:frame-id uuid/zero
:type :rect :type :rect
:name "rect"}}}) :name "rect"}))
chg {:type :del-shape chg {:type :del-obj
:id id :id uuid/zero}
:session-id (uuid/next)}
res (cp/process-changes data [chg])] res (cp/process-changes data [chg])]
(t/is (= 0 (count (:objects res))))))
(t/is (= 0 (count (:shapes res)))) (t/deftest process-change-mod-obj-abs-order
(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
(let [id1 (uuid/next) (let [id1 (uuid/next)
id2 (uuid/next) id2 (uuid/next)
id3 (uuid/next) id3 (uuid/next)
data (merge cp/default-page-data data (-> cp/default-page-data
{:shapes [id1 id2 id3]})] (assoc-in [:objects uuid/zero :shapes] [id1 id2 id3]))]
(t/testing "mov-canvas 1" (t/testing "abs order 1"
(let [chg {:type :mov-shape (let [chg {:type :mod-obj
:id uuid/zero
:operations [{:type :abs-order
:id id3 :id id3
:index 0 :index 0}]}
:session-id (uuid/next)}
res (cp/process-changes data [chg])] res (cp/process-changes data [chg])]
(t/is (= [id3 id1 id2] (:shapes res)))))
(t/testing "mov-canvas 2" ;; (clojure.pprint/pprint data)
(let [chg {:type :mov-shape ;; (clojure.pprint/pprint res)
:id id3
:index 100
:session-id (uuid/next)}
res (cp/process-changes data [chg])]
(t/is (= [id1 id2 id3] (:shapes res)))))
(t/testing "mov-canvas 3" (t/is (= [id3 id1 id2] (get-in res [:objects uuid/zero :shapes])))))
(let [chg {:type :mov-shape
:id id3 (t/testing "abs order 2"
:index 1 (let [chg {:type :mod-obj
:session-id (uuid/next)} :id uuid/zero
:operations [{:type :abs-order
:id id1
:index 100}]}
res (cp/process-changes data [chg])] 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])))))
)) ))

View file

@ -39,9 +39,20 @@
(t/is (= (:id data) (:id result))) (t/is (= (:id data) (:id result)))
(t/is (= (:name data) (:name result))) (t/is (= (:name data) (:name result)))
(t/is (= (:data data) (:data result))) (t/is (= (:data data) (:data result)))
(t/is (nil? (:share-token result)))
(t/is (= 0 (:version result))) (t/is (= 0 (:version result)))
(t/is (= 0 (:revn 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" (t/testing "query pages"
(let [data {::sq/type :pages (let [data {::sq/type :pages
:file-id (:id file) :file-id (:id file)
@ -55,6 +66,7 @@
(t/is (= 1 (count result))) (t/is (= 1 (count result)))
(t/is (= page-id (get-in result [0 :id]))) (t/is (= page-id (get-in result [0 :id])))
(t/is (= "test page" (get-in result [0 :name]))) (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/is (:id file) (get-in result [0 :file-id])))))
(t/testing "delete page" (t/testing "delete page"

View 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)))))
))