Composable handler->handler layers over Dream's primitives, with auth and permission POLICY injected so the layer is policy-free and testable: - middleware.sx: host/wrap-errors (JSON 500 via dream-catch-with), host/require-auth (bearer->principal via dream-bearer-token, JSON 401, injected token resolver), host/require-permission (lib/acl acl/permit? gate, JSON 403, injected resource extractor), host/pipeline (first = outermost) - feed.sx: POST /feed via host/feed-write-routes — auth ∘ ACL(post,feed) ∘ wrap-errors over host/feed-create (parse JSON body -> feed/post -> 201; non-object -> 400). Created activity reads back via GET /feed. - middleware suite (9) + feed write tests (6 new); conformance preloads now include the Datalog engine + ACL subsystem + Dream auth/error. ACL works with string atoms (no symbol coercion). Mute/prefs layer and sxtp.sx deferred to the next tick. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
2.3 KiB
Plaintext
50 lines
2.3 KiB
Plaintext
;; lib/host/feed.sx — Feed domain endpoints on the host. The first domain migrated
|
|
;; onto the SX host: read the activity timeline (GET /feed) and create activities
|
|
;; (POST /feed). Both go straight through the feed subsystem's public API; the
|
|
;; write path runs behind the host middleware stack (auth + ACL). Depends on
|
|
;; lib/feed/* + lib/host/handler.sx + lib/host/middleware.sx (write routes only).
|
|
|
|
;; ── read ───────────────────────────────────────────────────────────
|
|
|
|
;; GET /feed -> recent-first activities as a JSON envelope.
|
|
;; Query: ?actor=<id> (filter) ?limit=<n> (cap, applied after filtering).
|
|
(define host/feed-timeline
|
|
(fn (req)
|
|
(let ((base (feed/recent (feed/all)))
|
|
(actor (dream-query-param req "actor")))
|
|
(let ((filtered (if actor (feed/by-actor base actor) base))
|
|
(limit (dream-query-param req "limit")))
|
|
(let ((capped
|
|
(if limit (feed/take filtered (string->number limit)) filtered)))
|
|
(host/ok (feed/items capped)))))))
|
|
|
|
;; Public read route group.
|
|
(define host/feed-routes
|
|
(list
|
|
(dream-get "/feed" host/feed-timeline)))
|
|
|
|
;; ── write ──────────────────────────────────────────────────────────
|
|
|
|
;; POST /feed -> create an activity from the JSON body. Returns 201 + the created
|
|
;; (normalised) activity. Body must be a JSON object; anything else -> 400.
|
|
(define host/feed-create
|
|
(fn (req)
|
|
(let ((raw (dream-json-body req)))
|
|
(if (= (type-of raw) "dict")
|
|
(host/ok-status 201 (feed/post raw))
|
|
(host/error 400 "invalid activity")))))
|
|
|
|
;; Guarded write route group: POST /feed behind auth + ACL ("post" on "feed").
|
|
;; resolve : token -> principal | nil (injected auth policy, e.g. token lookup
|
|
;; against the identity subsystem). Errors thrown downstream become a JSON 500.
|
|
(define host/feed-write-routes
|
|
(fn (resolve)
|
|
(list
|
|
(dream-post "/feed"
|
|
(host/pipeline
|
|
(list
|
|
host/wrap-errors
|
|
(host/require-auth resolve)
|
|
(host/require-permission "post" (fn (req) "feed")))
|
|
host/feed-create)))))
|