jennex

Our Wedding Site
git clone git://git.alexkarle.com.com/jennex
Log | Files | Refs | README | LICENSE

commit 7fc93b61fd22f5899aacb369a5cafcd1818e4355 (patch)
parent 91e8cae0fc4cc52057210662f1ba7c0b8ec17413
Author: Alex Karle <alex@alexkarle.com>
Date:   Mon, 14 Nov 2022 00:59:19 -0500

rsvp: Add basic POST handler for updating rsvps

This was a solid bit of effort in that before now I wasn't super
familiar with the (chicken condition) module. I even got myself
in an endless loop because I didn't (call/cc) my way out of the
exception handler -.-

Another fun surprise was that sqlite isn't actually strictly
typed? It lets you store strings in integer columns / is dynamically
typed! Surprise! I'm relatively happy with how it turned out though.

Diffstat:
Msrc/db.scm | 55+++++++++++++++++++++++++++++++++++--------------------
Msrc/handler.scm | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
2 files changed, 105 insertions(+), 55 deletions(-)

diff --git a/src/db.scm b/src/db.scm @@ -9,6 +9,14 @@ id name party-id going meal-choice has-plus1 plus1-going plus1-meal-choice) +(set-record-printer! guest + (lambda (x o) + (fprintf o "(id: ~A name: ~A going: ~A meal: ~A)" + (guest-id x) + (guest-name x) + (guest-going x) + (guest-meal-choice x)))) + (define (get-guest-by-name name) (apply make-guest (first-row conn @@ -23,6 +31,20 @@ plus1_meal_choice FROM guests WHERE name = $name" name))) +(define (get-guest-by-id id) + (apply make-guest + (first-row conn +"SELECT + id, + name, + party_id, + going, + meal_choice, + has_plus1, + plus1_going, + plus1_meal_choice +FROM guests WHERE id = $name" id))) + (define (get-guests-in-party pid) (map-row make-guest conn "SELECT @@ -44,7 +66,7 @@ FROM guests WHERE party_id = $pid" pid)) (call/cc (lambda (c) (with-exception-handler - (lambda (x) (c '())) + (lambda (x) (print-error-message exn) (c '())) (lambda () (let* ((guest (get-guest-by-name name)) (party-id (guest-party-id guest))) @@ -53,22 +75,15 @@ FROM guests WHERE party_id = $pid" pid)) (list guest)))))))) (define (update-guest g) - (call/cc - (lambda (c) - (with-exception-handler - (lambda (x) (print x) (c #f)) - (lambda () - (update conn " -UPDATE guests SET - name = $1, - going = $2, - meal_choice = $3, - plus1_going = $4, - plus1_meal_choice = $5 -WHERE id = $6" - (guest-name g) - (guest-going g) - (guest-meal-choice g) - (guest-plus1-going g) - (guest-plus1-meal-choice g) - (guest-id g))))))) + (update conn " + UPDATE guests SET + going = $2, + meal_choice = $3, + plus1_going = $4, + plus1_meal_choice = $5 + WHERE id = $6" + (guest-going g) + (guest-meal-choice g) + (guest-plus1-going g) + (guest-plus1-meal-choice g) + (guest-id g))) diff --git a/src/handler.scm b/src/handler.scm @@ -5,10 +5,13 @@ intarweb sxml-serializer sql-null - (chicken string)) + (chicken string) + (chicken condition)) (load "db.scm") +(define email '(a (@ (href "mailto:rsvp@jennex.org")) "rsvp@jennex.org")) + (define routes ;; See uri-match for format: http://wiki.call-cc.org/eggref/5/uri-match#routes-format ;; @@ -55,46 +58,35 @@ (button "Lookup")) (p "Please let us know by DATE whether you can make it!"))))) -(define (guest-to-form g) - ;; Given a guest record, generate a SXML form for their settings - ;; TODO: expand plus-1's - (define (get-going-attrs val) - `((name ,(conc (number->string (guest-id g)) "__going")) - (value ,val) - (type "radio") - (required "true") - ,@(let ((going (guest-going g))) - (cond ((equal? val "no") (if (eq? going 0) '((checked)) '())) - ((equal? val "yes") (if (eq? going 1) '((checked)) '())) - ((equal? val "null") (if (sql-null? going) '((checked)) '())) - (else (error "Bad val")))))) +(define key-to-getter + `(("going" . ,guest-going) + ("meal-choice" . ,guest-meal-choice))) - (define (get-meal-attrs val) - `((name ,(conc (number->string (guest-id g)) "__meal_choice")) +(define (guest-to-form g) + (define (get-attrs key val) + `((name ,(conc (number->string (guest-id g)) "__" key)) (value ,val) (type "radio") (required "true") - ,@(if (equal? (guest-meal-choice g) val) + ,@(let ((getter (assoc key key-to-getter))) + (if (and getter (equal? ((cdr getter) g) val)) '((checked)) - '()))) - + '())))) ;; TODO: add notes section for allergies, etc `((div (@ (class "guest")) (h4 ,(guest-name g)) (fieldset (legend "Will You be Attending?") - (label (input (@ ,@(get-going-attrs "yes"))) "Yes!") - (br) - (label (input (@ ,@(get-going-attrs "no"))) "No :(") + (label (input (@ ,@(get-attrs "going" 1))) "Yes!") (br) - (label (input (@ ,@(get-going-attrs "null"))) "Not Sure Yet...")) + (label (input (@ ,@(get-attrs "going" 0))) "No :(")) (fieldset (legend "Meal Choice") - (label (input (@ ,@(get-meal-attrs "chicken"))) "Chicken") + (label (input (@ ,@(get-attrs "meal-choice" "chicken"))) "Chicken") (br) - (label (input (@ ,@(get-meal-attrs "beef"))) "Beef") + (label (input (@ ,@(get-attrs "meal-choice" "beef"))) "Beef") (br) - (label (input (@ ,@(get-meal-attrs "vegetarian"))) "Chicken"))))) + (label (input (@ ,@(get-attrs "meal-choice" "vegetarian"))) "Vegetarian"))))) (define (route-get-rsvp c) ;; TODO: consider a POST instead of GET to prevent people from sharing @@ -115,14 +107,57 @@ `((h2 "RSVP") (p "Sorry! We can't find anyone under the name '" ,name "'. Please double check the spelling and if it looks like a " - "mistake on our end email us at " - (a (@ (href "mailto:rsvp@jennex.org")) "rsvp@jennex.org") ".")))))))) + "mistake on our end email us at " ,email)))))))) + + +(define key-to-setter + ;; Just an a-list of input names -> guest-setter functions + ;; (allowing the request to specify any string is dangerous) + `((going . ,guest-going-set!) + (meal-choice . ,guest-meal-choice-set!) + (plus1-going . ,guest-plus1-going-set!) + (plus1-meal-choice . ,guest-plus1-meal-choice-set!))) (define (route-post-rsvp c) - (let ((fdata (read-urlencoded-request-data (current-request)))) - (send-sxml - (template-page - `((h2 "RSVP") - (p "Success! Thanks for RSVP-ing.") - (p (a (@ (href "/rsvp")) "Edit your response")) - (p (a (@ (href "https://jennex.org")) "Read more about the event"))))))) + (call/cc + (lambda (c) + (with-exception-handler + (lambda (exn) + (print-error-message exn) + (send-sxml + (template-page + `((h2 "RSVP") + ;; TODO: log it better... + (p "There was an error saving your response please try again " + "and if it continues to fail, reach out to us at " ,email)))) + (c #f)) + (lambda () + ;; The data is in pairs of (ID__key . val), so the first thing to do + ;; is to walk through that data and build up a view of each guest + (let loop ((guests '()) (fdata (read-urlencoded-request-data (current-request)))) + (if (null? fdata) ;; Done parsing all our form data + (begin + ;; This will raise if update-guest fails (so we get an error page) + (for-each print (map update-guest (map cdr guests))) + (send-sxml + (template-page + `((h2 "RSVP") + (p "Success! Thanks for RSVP-ing.") + (p (a (@ (href "/")) "Edit your response")) + (p (a (@ (href "https://jennex.org")) "Read more about the event")))))) + (let* ((input (car fdata)) + (name (car input)) + (value (cdr input)) + (split (string-split (symbol->string name) "__")) + (id (string->number (car split))) + (cached-guest (assoc id guests)) + (key (string->symbol (cadr split))) + (setter! (cdr (assoc key key-to-setter)))) + (if cached-guest + (begin + (print "setting to " value) + (setter! (cdr cached-guest) value) + (loop guests (cdr fdata))) + (let ((guest (get-guest-by-id id))) + (setter! guest value) + (loop (cons (cons id guest) guests) (cdr fdata))))))))))))