🚧 Initial work on ops based page data updates.

This commit is contained in:
Andrey Antukh 2019-12-14 21:24:38 +01:00
parent 5b96e1e9fd
commit db768f356b
5 changed files with 94 additions and 44 deletions

View file

@ -68,7 +68,7 @@ CREATE TABLE IF NOT EXISTS project_pages (
metadata bytea NULL DEFAULT NULL metadata bytea NULL DEFAULT NULL
); );
CREATE TABLE IF NOT EXISTS project_page_history ( CREATE TABLE IF NOT EXISTS project_page_snapshots (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id uuid NULL REFERENCES users(id) ON DELETE SET NULL, user_id uuid NULL REFERENCES users(id) ON DELETE SET NULL,
@ -81,7 +81,8 @@ CREATE TABLE IF NOT EXISTS project_page_history (
pinned bool NOT NULL DEFAULT false, pinned bool NOT NULL DEFAULT false,
label text NOT NULL DEFAULT '', label text NOT NULL DEFAULT '',
data bytea NOT NULL data bytea NOT NULL,
operations bytea NULL DEFAULT NULL
); );
-- Indexes -- Indexes
@ -94,8 +95,8 @@ CREATE INDEX project_files__project_id__idx ON project_files(project_id);
CREATE INDEX project_pages__user_id__idx ON project_pages(user_id); CREATE INDEX project_pages__user_id__idx ON project_pages(user_id);
CREATE INDEX project_pages__file_id__idx ON project_pages(file_id); CREATE INDEX project_pages__file_id__idx ON project_pages(file_id);
CREATE INDEX project_page_history__page_id__idx ON project_page_history(page_id); CREATE INDEX project_page_snapshots__page_id__idx ON project_page_snapshots(page_id);
CREATE INDEX project_page_history__user_id__idx ON project_page_history(user_id); CREATE INDEX project_page_snapshots__user_id__idx ON project_page_snapshots(user_id);
-- Triggers -- Triggers
@ -152,6 +153,6 @@ CREATE TRIGGER project_pages__modified_at__tgr
BEFORE UPDATE ON project_pages BEFORE UPDATE ON project_pages
FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
CREATE TRIGGER project_page_history__modified_at__tgr CREATE TRIGGER project_page_snapshots__modified_at__tgr
BEFORE UPDATE ON project_page_history BEFORE UPDATE ON project_page_snapshots
FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); FOR EACH ROW EXECUTE PROCEDURE update_modified_at();

View file

@ -56,11 +56,11 @@
(-> (db/query-one conn [sql id user file-id name ordering data mdata]) (-> (db/query-one conn [sql id user file-id name ordering data mdata])
(p/then' decode-row)))) (p/then' decode-row))))
;; --- Mutation: Update Page ;; --- Mutation: Update Page Data
(declare select-page-for-update) (declare select-page-for-update)
(declare update-page) (declare update-page-data)
(declare update-history) (declare insert-page-snapshot)
(s/def ::update-project-page-data (s/def ::update-project-page-data
(s/keys :req-un [::id ::user ::data])) (s/keys :req-un [::id ::user ::data]))
@ -73,13 +73,13 @@
(let [data (blob/encode data) (let [data (blob/encode data)
version (inc version) version (inc version)
params (assoc params :id id :data data :version version)] params (assoc params :id id :data data :version version)]
(p/do! (update-page conn params) (p/do! (update-page-data conn params)
(update-history conn params) (insert-page-snapshot conn params)
(select-keys params [:id :version])))))) (select-keys params [:id :version]))))))
(defn- select-page-for-update (defn- select-page-for-update
[conn id] [conn id]
(let [sql "select p.id, p.version, p.file_id (let [sql "select p.id, p.version, p.file_id, p.data
from project_pages as p from project_pages as p
where p.id = $1 where p.id = $1
and deleted_at is null and deleted_at is null
@ -87,8 +87,8 @@
(-> (db/query-one conn [sql id]) (-> (db/query-one conn [sql id])
(p/then' su/raise-not-found-if-nil)))) (p/then' su/raise-not-found-if-nil))))
(defn- update-page (defn- update-page-data
[conn {:keys [id name version data metadata]}] [conn {:keys [id name version data]}]
(let [sql "update project_pages (let [sql "update project_pages
set version = $1, set version = $1,
data = $2 data = $2
@ -96,12 +96,12 @@
(-> (db/query-one conn [sql version data id]) (-> (db/query-one conn [sql version data id])
(p/then' su/constantly-nil)))) (p/then' su/constantly-nil))))
(defn- update-history (defn- insert-page-snapshot
[conn {:keys [user id version data]}] [conn {:keys [user id version data operations]}]
(let [sql "insert into project_page_history (user_id, page_id, version, data) (let [sql "insert into project_page_snapshots (user_id, page_id, version, data, operations)
values ($1, $2, $3, $4)"] values ($1, $2, $3, $4, $5)
(-> (db/query-one conn [sql user id version data]) returning id, version, operations"]
(p/then' su/constantly-nil)))) (db/query-one conn [sql user id version data operations])))
;; --- Mutation: Rename Page ;; --- Mutation: Rename Page
@ -126,23 +126,60 @@
(-> (db/query-one db/pool [sql id name]) (-> (db/query-one db/pool [sql id name])
(p/then su/constantly-nil)))) (p/then su/constantly-nil))))
;; --- Mutation: Update Page Metadata ;; --- Mutation: Update Page
;; (s/def ::update-page-metadata ;; A generic, Ops based (granular) page update method.
;; (s/keys :req-un [::user ::project-id ::name ::metadata ::id]))
;; (sm/defmutation ::update-page-metadata (s/def ::operations
;; [{:keys [id user project-id name metadata]}] (s/coll-of ::cp/opeation :kind vector?))
;; (let [sql "update pages
;; set name = $3, (s/def ::update-project-page
;; metadata = $4 (s/keys :opt-un [::id ::user ::version ::operations]))
;; where id = $1
;; and user_id = $2 (declare update-project-page)
;; and deleted_at is null (declare retrieve-lagged-operations)
;; returning *"
;; mdata (blob/encode metadata)] (sm/defmutation ::update-project-page
;; (-> (db/query-one db/pool [sql id user name mdata]) [{:keys [id user] :as params}]
;; (p/then' decode-row)))) (db/with-atomic [conn db/pool]
(p/let [{:keys [file-id] :as page} (select-page-for-update conn id)]
(files/check-edition-permissions! conn user file-id)
(update-project-page conn page params))))
(defn- update-project-page
[conn page params]
(when (> (:version page)
(:version params))
(ex/raise :type :validation
:code :version-conflict
:hint "The incoming version is greater that stored version."
:context {:incoming-version (:version params)
:stored-version (:version page)}))
(let [ops (:operations params)
data (-> (:data page)
(blob/decode)
(cp/process-ops ops)
(blob/encode))
page (assoc page
:data data
:version (inc (:version page))
:operations (blob/encode ops))]
(-> (update-page-data conn page)
(p/then (fn [_] (insert-page-snapshot conn page)))
(p/then (fn [s] (retrieve-lagged-operations conn s params))))))
(su/defstr sql:lagged-snapshots
"select s.id, s.version, s.operations,
s.created_at, s.modified_at, s.user_id
from project_page_snapshots as s
where s.page_id = $1
and s.version > $2")
(defn- retrieve-lagged-operations
[conn snapshot params]
(let [sql sql:lagged-snapshots]
(-> (db/query conn [sql (:id params) (:version params)])
(p/then (partial mapv decode-row)))))
;; --- Mutation: Delete Page ;; --- Mutation: Delete Page
@ -158,12 +195,15 @@
(files/check-edition-permissions! conn user (:file-id page)) (files/check-edition-permissions! conn user (:file-id page))
(delete-page conn id)))) (delete-page conn id))))
(su/defstr sql:delete-page
"update project_pages
set deleted_at = clock_timestamp()
where id = $1
and deleted_at is null")
(defn- delete-page (defn- delete-page
[conn id] [conn id]
(let [sql "update project_pages (let [sql sql:delete-page]
set deleted_at = clock_timestamp()
where id = $1
and deleted_at is null"]
(-> (db/query-one conn [sql id]) (-> (db/query-one conn [sql id])
(p/then su/constantly-nil)))) (p/then su/constantly-nil))))
@ -178,7 +218,7 @@
;; (some-> (db/fetch-one conn sqlv) ;; (some-> (db/fetch-one conn sqlv)
;; (decode-row)))) ;; (decode-row))))
;; (s/def ::label ::us/string) ;; (s/def ::label ::cs/string)
;; (s/def ::update-page-history ;; (s/def ::update-page-history
;; (s/keys :req-un [::user ::id ::pinned ::label])) ;; (s/keys :req-un [::user ::id ::pinned ::label]))

View file

@ -139,8 +139,9 @@
;; --- Helpers ;; --- Helpers
(defn decode-row (defn decode-row
[{:keys [data metadata] :as row}] [{:keys [data metadata operations] :as row}]
(when row (when row
(cond-> row (cond-> row
data (assoc :data (blob/decode data)) data (assoc :data (blob/decode data))
metadata (assoc :metadata (blob/decode metadata))))) metadata (assoc :metadata (blob/decode metadata))
operations (assoc :operations (blob/decode operations)))))

View file

@ -7,6 +7,7 @@
[uxbox.http :as http] [uxbox.http :as http]
[uxbox.services.mutations :as sm] [uxbox.services.mutations :as sm]
[uxbox.services.queries :as sq] [uxbox.services.queries :as sq]
[uxbox.util.uuid :as uuid]
[uxbox.tests.helpers :as th])) [uxbox.tests.helpers :as th]))
(t/use-fixtures :once th/state-init) (t/use-fixtures :once th/state-init)
@ -34,7 +35,9 @@
pf @(th/create-project-file db/pool (:id user) (:id proj) 1) pf @(th/create-project-file db/pool (:id user) (:id proj) 1)
data {::sm/type :create-project-page data {::sm/type :create-project-page
:data {} :data {:canvas []
:shapes []
:shapes-by-id {}}
:metadata {} :metadata {}
:file-id (:id pf) :file-id (:id pf)
:ordering 1 :ordering 1
@ -57,7 +60,9 @@
page @(th/create-project-page db/pool (:id user) (:id file) 1) page @(th/create-project-page db/pool (:id user) (:id file) 1)
data {::sm/type :update-project-page-data data {::sm/type :update-project-page-data
:id (:id page) :id (:id page)
:data {:shapes [1 2 3]} :data {:shapes [(uuid/next)]
:canvas []
:shapes-by-id {}}
:file-id (:id file) :file-id (:id file)
:user (:id user)} :user (:id user)}
out (th/try-on! (sm/handle data))] out (th/try-on! (sm/handle data))]

View file

@ -82,6 +82,9 @@
"A marker protocol for mark events that alters the "A marker protocol for mark events that alters the
page and is subject to perform a backend synchronization.") page and is subject to perform a backend synchronization.")
(defprotocol IPageOps
(-ops [_] "Get a list of ops for the event."))
(defn page-update? (defn page-update?
[o] [o]
(or (satisfies? IPageDataUpdate o) (or (satisfies? IPageDataUpdate o)