;; lib/dream/demos/todo.sx — CRUD todo list with forms + CSRF (todo.ml). ;; An in-memory store holds items; add/toggle/delete go through POST forms guarded ;; by the CSRF middleware. User text is HTML-escaped on render (dream-escape). ;; Wires session -> csrf -> router. (define dream-todo-store (fn () (let ((items (list)) (next-id 0)) {:all (fn () items) :add (fn (text) (begin (set! next-id (+ next-id 1)) (set! items (concat items (list {:id next-id :text text :done false}))) next-id)) :delete (fn (id) (set! items (filter (fn (it) (not (= (get it :id) id))) items))) :toggle (fn (id) (set! items (map (fn (it) (if (= (get it :id) id) (assoc it :done (not (get it :done))) it)) items)))}))) (define dr/todo-render (fn (store req) (str "" "
" (dream-csrf-tag req) "
"))) (define dream-todo-index (fn (store) (fn (req) (dream-html (dr/todo-render store req))))) (define dream-todo-add (fn (store) (fn (req) (let ((r (dream-form req))) (if (dream-ok? r) (begin ((get store :add) (get (dream-ok-value r) "text")) (dream-redirect "/")) (dream-html-status 403 (str "Rejected: " (dream-err-reason r)))))))) (define dream-todo-toggle (fn (store) (fn (req) (let ((r (dream-form req))) (if (dream-ok? r) (begin ((get store :toggle) (parse-int (dream-param req "id"))) (dream-redirect "/")) (dream-html-status 403 "Rejected")))))) (define dream-todo-delete (fn (store) (fn (req) (let ((r (dream-form req))) (if (dream-ok? r) (begin ((get store :delete) (parse-int (dream-param req "id"))) (dream-redirect "/")) (dream-html-status 403 "Rejected")))))) (define dream-todo-app-with (fn (store backend secret) ((dream-sessions backend) ((dream-csrf secret) (dream-router (list (dream-get "/" (dream-todo-index store)) (dream-post "/add" (dream-todo-add store)) (dream-post "/toggle/:id" (dream-todo-toggle store)) (dream-post "/delete/:id" (dream-todo-delete store)))))))) ;; entry: (dream-run (dream-todo-app-with (dream-todo-store) (dream-memory-sessions) "change-me"))