Files
rose-ash/lib/dream/demos/todo.sx
giles 0366373c8a
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m9s
dream: HTML escaping (dream-escape) + fix XSS hole in todo demo + 11 tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:18:49 +00:00

97 lines
2.7 KiB
Plaintext

;; 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
"<ul>"
(reduce
(fn
(acc it)
(str
acc
"<li>"
(if (get it :done) "[x] " "[ ] ")
(dream-escape (get it :text))
"</li>"))
""
((get store :all)))
"</ul>"
"<form method=\"post\" action=\"/add\">"
(dream-csrf-tag req)
"<input name=\"text\"><button>Add</button></form>")))
(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"))