Skip to content

Commit 0179f8d

Browse files
Very large syncing/serialization updates
Bulk sync posting is now working great. Also, fixed sync-id inclusion bug in GET /sync. also working great. Converted defvalidator to use new method by default (compatible with adefun, the new async function definer) and converted all validators to use :old t if not converted yet. updating json parser to jonathan, although having a few bumps in the road (nil -> [] vs null was a big one, and now a simple parser error) but going to stick with it since performance is a big concern. ripped out note bulk save endpoint/functions because they are fucking stupid, especially with the bulk sync api. there are probably more changes i'm not codifying, but it's been a long week (or more?) and i don't remember everything. read the diff.
1 parent 4dacfac commit 0179f8d

15 files changed

+444
-204
lines changed

controllers/notes.lisp

-13
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,3 @@
214214
(track "file-delete" `(:shared ,(when persona-id t)) req)
215215
(send-json res t))))
216216

217-
(defroute (:put "/api/notes/batch") (req res)
218-
"Batch edit. Allows passing in a persona ID, which will be used in place of
219-
the current user ID when validating permissions."
220-
(catch-errors (res)
221-
(alet* ((user-id (user-id req))
222-
(persona-id (post-var req "persona"))
223-
(batch-edit-data (post-var req "data"))
224-
(nil (if persona-id
225-
(with-valid-persona (persona-id user-id)
226-
(batch-note-edit persona-id batch-edit-data))
227-
(batch-note-edit user-id batch-edit-data))))
228-
(send-json res t))))
229-

controllers/sync.lisp

+18-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131
(alet ((user (get-user-by-id user-id))
3232
(keychain (get-user-keychain user-id))
3333
(personas (get-user-personas user-id))
34-
(boards (get-user-boards user-id :get-persona-boards t :get-personas t))
3534
(global-sync-id (get-latest-sync-id)))
3635
;; notes require all our board ids, so load them here
37-
(alet* ((notes (get-notes-from-board-ids (map 'list (lambda (b) (gethash "id" b)) boards)))
36+
(alet* ((boards (get-all-boards user-id (map 'list (lambda (p) (gethash "id" p)) personas)))
37+
(board-ids (map 'list (lambda (b) (gethash "id" b)) boards))
38+
(notes (get-all-notes user-id board-ids))
3839
(files (remove-if-not (lambda (note)
3940
(and (hget note '("file"))
4041
(hget note '("file" "hash"))))
@@ -62,8 +63,21 @@
6263

6364
(defroute (:post "/api/v2/sync") (req res)
6465
"Bulk sync API. Accepts any number of sync items and applies the updates to
65-
the profile of the authed user."
66-
)
66+
the profile of the authed user.
67+
68+
Note that the items are added in sequence and if any one in the sequence
69+
fails, we abort and send back the successes and failures. This is because
70+
many of the items need to be added in a specific sequence in order to work
71+
correctly (for instance, a keychain entry for a board needs to be synced
72+
before the board itself). Catching a failure in the sequence allows the
73+
client to try again whilst still preserving the original order of the sync
74+
items."
75+
(catch-errors (res)
76+
(alet* ((user-id (user-id req))
77+
(sync-items (jonathan:parse (babel:octets-to-string (request-body req)) :as :hash-table))
78+
(synced (bulk-sync user-id sync-items)))
79+
(send-json res synced))))
80+
6781

6882
;;; ----------------------------------------------------------------------------
6983
;;; deprecated stuff

lib/util.lisp

+14-6
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@
5555
(error (format nil "Bad index name passed to db-index: ~a" index)))
5656
(format nil "~a.v~a" (string-downcase (string index-key)) (getf index-entry :version))))
5757

58+
(defmethod jonathan:%to-json ((_ (eql nil)))
59+
(jonathan:%write-string "null"))
60+
5861
(defun to-json (object &key indent)
5962
"Convert an object to JSON."
60-
(with-output-to-string (s)
61-
(let ((js (yason:make-json-output-stream s :indent indent)))
62-
(yason:encode object js))))
63+
(declare (ignore indent))
64+
(jonathan:to-json object))
6365

6466
(defun send-json (response object &key (status 200))
6567
"Wraps sending of JSON back to the client."
@@ -96,8 +98,7 @@
9698
(defun copy-hash (hash)
9799
"Deep copy a hash table."
98100
;; lazy way
99-
(yason:parse (with-output-to-string (s)
100-
(yason:encode hash s))))
101+
(jonathan:parse (jonathan:to-json hash) :as :hash-table))
101102

102103
(defun add-id (hash-object &key (id-key "id"))
103104
"Add a mongo id to a hash table object."
@@ -140,6 +141,9 @@
140141
(setf body (cdr body)))
141142
`(defun ,name ,args
142143
,(if (stringp docstring) docstring "")
144+
,(when (eq (caar body) 'declare)
145+
(prog1 (car body)
146+
(setf body (cdr body))))
143147
(catcher
144148
(progn ,@body)
145149
(error (e)
@@ -151,6 +155,10 @@
151155
:error e
152156
:function ',name)))))))
153157

158+
;; !!!!!!!!!!!!!!!!!!
159+
;; !!! DEPRECATED !!!
160+
;; !!!!!!!!!!!!!!!!!!
161+
;; do not build new functions that use this macro. use adefun instead!
154162
(defmacro defafun (name (future-var &key (forward-errors t)) args &body body)
155163
"Define an asynchronous function with a returned promise that will be finished
156164
when the function completes. Also has the option to forward all async errors
@@ -266,7 +274,7 @@
266274

267275
(defun jprint (db-result)
268276
"Pretty printer for JSON (mainly for database results)."
269-
(yason:encode db-result (yason:make-json-output-stream *standard-output* :indent 2)))
277+
(to-json db-result :indent 2))
270278

271279
(defmacro with-test (&body body)
272280
"Makes testing async functions easier by abstracting an extremely common

lib/validation.lisp

+12-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
;; check required fields
3434
(when (and (getf entry :required)
3535
(not edit)
36-
(not obj-val))
36+
(not exists))
3737
(cond ((and default-val (symbolp default-val))
3838
(setf obj-val (funcall default-val)))
3939
(default-val
@@ -98,22 +98,27 @@
9898
(remhash key object))))
9999
nil)
100100

101-
(defmacro defvalidator (name validation-form)
101+
(defmacro defvalidator (name validation-form &key old)
102102
"Makes defining a validation function for a data type simpler."
103103
`(progn
104104
(setf (getf *validation-forms* ',name) ',validation-form)
105-
(defmacro ,name ((object future &key edit) &body body)
105+
(defmacro ,name (,(append '(object)
106+
(when old '(future))
107+
'(&key edit))
108+
&body body)
106109
(let ((validation (gensym "validation"))
107110
(validation-form-var (gensym "validation-form"))
108111
(future-var (gensym "future")))
109-
`(let* ((,future-var ,future)
112+
`(let* ((,future-var ,(when ,old future))
110113
(,validation-form-var (getf *validation-forms* ,'',name))
111114
(,validation (do-validate ,object ,validation-form-var :edit ,edit)))
115+
(declare (ignorable ,future-var))
112116
(if ,validation
113-
(signal-error ,future-var (make-instance 'validation-failed
114-
:msg (format nil "Validation failed: ~s~%" ,validation)))
117+
,(if ,old
118+
`(signal-error ,future-var (make-instance 'validation-failed
119+
:msg (format nil "Validation failed: ~s~%" ,validation)))
120+
`(error 'validation-failed :msg (format nil "Validation failed: ~s~%" ,validation)))
115121
(progn ,@body)))))))
116-
117122
;(defmacro defvalidator (name validation-form)
118123
; "Makes defining a validation function for a data type simpler."
119124
; `(progn

models/boards.lisp

+63-12
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
(defvalidator validate-board
44
(("id" :type id :required t)
55
("user_id" :type id :required t)
6+
("parent_id" :type id)
67
("keys" :type sequence :required t :coerce simple-vector)
7-
("body" :type cl-async-util:bytes-or-string)))
8+
("body" :type cl-async-util:bytes-or-string))
9+
:old t)
810

911
(defafun populate-boards-data (future) (boards &key (get-privs t) get-notes get-personas)
1012
"Populate certain information given a list of boards."
@@ -32,9 +34,19 @@
3234
(finish future boards))))
3335
(finish future boards)))
3436

35-
(defafun get-affected-users-from-board-ids (future) (board-ids)
37+
(adefun get-all-boards (user-id persona-ids)
38+
"Given a user id and list of persona ids, get all boards this user has access
39+
to."
40+
(declare (ignore persona-ids))
41+
;; TODO: actually build this function out and make it useful. until then, we
42+
;; rely on old (and slow) tricks
43+
(get-user-boards user-id :get-persona-boards t :get-personas t))
44+
45+
(adefun get-affected-users-from-board-ids (board-ids)
3646
"For all given board-ids (list), find users that will be affected by changes
3747
to those boards or items in those boards. Returns a list of user-ids."
48+
(unless board-ids
49+
(return-from get-affected-users-from-board-ids))
3850
(alet* ((sock (db-sock))
3951
(query (r:r
4052
(:attr
@@ -64,9 +76,9 @@
6476
(board-user-ids (r:to-array sock cursor))
6577
(nil (r:stop sock cursor)))
6678
(r:disconnect sock)
67-
(finish future (concatenate 'vector shared-user-ids board-user-ids))))
79+
(concatenate 'vector shared-user-ids board-user-ids)))
6880

69-
(defafun get-user-boards (future) (user-id &key get-persona-boards get-notes get-personas)
81+
(adefun get-user-boards (user-id &key get-persona-boards get-notes get-personas)
7082
"Get all boards for a user."
7183
(alet* ((sock (db-sock))
7284
(query (r:r (:get-all
@@ -84,13 +96,15 @@
8496
(boards-populated (populate-boards-data all-boards
8597
:get-notes get-notes
8698
:get-personas get-personas)))
87-
(finish future boards-populated))))
99+
boards-populated)))
88100

89-
(defafun get-all-user-board-ids (future) (user-id &key shared)
101+
(defafun get-all-user-board-ids (future) (user-id &key shared persona-ids)
90102
"Gets ALL a user's board IDs, with option to specify grabbing shared boards."
91-
(alet* ((persona-ids (if shared
92-
(get-user-persona-ids user-id)
93-
#()))
103+
(alet* ((persona-ids (if persona-ids
104+
persona-ids
105+
(if shared
106+
(get-user-persona-ids user-id)
107+
#())))
94108
(sock (db-sock)))
95109
(flet ((get-user-board-ids (append)
96110
(alet* ((query (r:r (:attr
@@ -116,6 +130,44 @@
116130
(r:stop sock cursor)
117131
(get-user-board-ids board-ids))))))
118132

133+
(adefun get-user-board-perms (user-id &key min-perms)
134+
"Grab all a users boards, including shared, with permissions."
135+
(alet* ((persona-ids (get-user-persona-ids user-id))
136+
(user-board-ids (get-all-user-board-ids user-id))
137+
(sock (db-sock))
138+
(qry (r:r (:pluck
139+
(:zip
140+
(:eq-join
141+
(:get-all
142+
(:table "boards_personas_link")
143+
(coerce persona-ids 'list)
144+
:index (db-index "boards_personas_link" "to"))
145+
"board_id"
146+
(:table "boards")))
147+
(list "user_id" "board_id" "perms"))))
148+
(cursor (r:run sock qry))
149+
(shared (r:to-array sock cursor))
150+
(index (hash)))
151+
(r:stop/disconnect sock cursor)
152+
;; set permissions for shared boards
153+
(loop for entry across shared
154+
for id = (gethash "board_id" entry)
155+
for perms = (gethash "perms" entry) do
156+
(when (or (not min-perms)
157+
(<= min-perms perms))
158+
(setf (gethash id index) (hash ("id" id)
159+
("owner" (gethash "user_id" entry))
160+
("perms" perms)))))
161+
;; set user-owned to the "owned" permission (3) in the index
162+
(loop for id across user-board-ids
163+
for perms = 3 do
164+
(when (or (not min-perms)
165+
(<= min-perms perms))
166+
(setf (gethash id index) (hash ("id" id)
167+
("owner" user-id)
168+
("perms" perms)))))
169+
index))
170+
119171
(defafun get-persona-boards (future) (persona-id &key populate get-notes)
120172
"Get all boards for a persona."
121173
(alet* ((sock (db-sock))
@@ -177,7 +229,6 @@
177229
(defafun add-board (future) (user-id board-data)
178230
"Save a board with a user."
179231
(setf (gethash "user_id" board-data) user-id)
180-
(add-id board-data)
181232
(let ((cid (gethash "cid" board-data)))
182233
(validate-board (board-data future)
183234
(alet* ((sock (db-sock))
@@ -260,7 +311,7 @@
260311
(defafun get-user-board-permissions (future) (user/persona-id board-id)
261312
"Returns an integer used to determine a user/persona's permissions for the
262313
given board.
263-
314+
264315
0 == no permissions
265316
1 == read permissions
266317
2 == update permissions
@@ -388,7 +439,7 @@
388439
(finish future entry)))
389440

390441
(defafun add-board-remote-invite (future) (user-id board-id from-persona-id invite-id permission-value to-email)
391-
"Creates a remote (ie email) invite permission record on a board so the
442+
"Creates a remote (ie email) invite permission record on a board so the
392443
recipient of an invite can join the board without knowing what their account
393444
will be in advance."
394445
(alet* ((email (obscure-email to-email)))

models/email.lisp

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Please respond in a timely manner!"))
8989
(finish future t)
9090
;; error. grab the message and signal
9191
(let* ((res (babel:octets-to-string res))
92-
(hash (yason:parse res))
92+
(hash (jonathan:parse res :as :hash-table))
9393
(msg (gethash "error" hash))
9494
(msg (if (hash-table-p msg)
9595
(gethash "message" msg)

models/feedback.lisp

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
(defvalidator validate-feedback
44
(("user_id" :type id :required t)
55
("email" :type string :required t)
6-
("body" :type string :required t)))
6+
("body" :type string :required t))
7+
:old t)
78

89
(defafun send-feedback (future) (user-id feedback-data)
910
"Send feedback posted by a user to the proper channels (email, most likely)."

models/keychain.lisp

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
("user_id" :type id :required t)
66
("type" :type string :required t :max-length 24)
77
("item_id" :type string :required t)
8-
("body" :type string :required t)))
8+
("body" :type string :required t))
9+
:old t)
910

1011
(defafun get-keychain-entry-by-id (future) (key-id)
1112
"Get a keychain entry by its id."
@@ -34,7 +35,6 @@
3435
(defafun add-keychain-entry (future) (user-id key-data)
3536
"Add a new keychain entry for the given user."
3637
(setf (gethash "user_id" key-data) user-id)
37-
(add-id key-data)
3838
(validate-keychain-entry (key-data future)
3939
(alet* ((sock (db-sock))
4040
(query (r:r (:insert

models/messages.lisp

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
("from" :type id :required t)
66
("to" :type id :required t)
77
("keys" :type sequence :required t :coerce simple-vector)
8-
("data" :type cl-async-util:bytes-or-string)))
8+
("data" :type cl-async-util:bytes-or-string))
9+
:old t)
910

1011
(defafun get-messages-by-persona (future) (persona-id &key (after "") (index "get_messages_to"))
1112
"Gets messages for a persona. If a message ID is specified for :after, will

0 commit comments

Comments
 (0)