sx-pub Phase 3: federation — outbox, inbox, follow, delivery, HTTP signatures
New endpoints: - GET /pub/outbox — paginated activity feed - POST /pub/inbox — receive Follow/Accept/Publish from remote servers - POST /pub/follow — follow a remote sx-pub server - GET /pub/followers — list accepted followers - GET /pub/following — list who we follow Federation mechanics: - HTTP Signature generation (RSA-SHA256) for signed outgoing requests - HTTP Signature verification for incoming requests - Auto-accept Follow → store follower → send Accept back - Accept handling → update following state - Publish mirroring → pin remote CID to local IPFS - deliver_to_followers → fan out signed activities to all follower inboxes - Publish now records activity in outbox for federation delivery Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -215,3 +215,125 @@
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
(set-response-header "Cache-Control" "public, max-age=31536000, immutable")
|
||||
(get data "content")))))
|
||||
|
||||
|
||||
;; ==========================================================================
|
||||
;; Phase 3: Federation — outbox, inbox, follow, followers, following
|
||||
;; ==========================================================================
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Outbox
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler pub-outbox
|
||||
:path "/pub/outbox"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((page (helper "request-arg" "page" ""))
|
||||
(data (helper "pub-outbox-data" page)))
|
||||
(do
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
(let ((items (map (fn (a)
|
||||
(str "\n (" (get a "type")
|
||||
" :object-type \"" (get a "object-type") "\""
|
||||
" :published \"" (get a "published") "\""
|
||||
" :cid \"" (get a "cid") "\")"))
|
||||
(get data "items"))))
|
||||
(str
|
||||
"(SxOutbox"
|
||||
"\n :total " (get data "total")
|
||||
"\n :page " (get data "page")
|
||||
(join "" items) ")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Inbox
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler pub-inbox
|
||||
:path "/pub/inbox"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((body (helper "pub-request-body")))
|
||||
(if (= body "")
|
||||
(do
|
||||
(set-response-status 400)
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
"(Error :message \"Empty body\")")
|
||||
(let ((result (helper "pub-process-inbox" body)))
|
||||
(do
|
||||
(set-response-status 202)
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
(str "(Accepted :result " (str result) ")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Follow a remote server
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler pub-follow
|
||||
:path "/pub/follow"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((actor-url (helper "request-form" "actor_url" "")))
|
||||
(if (= actor-url "")
|
||||
(do
|
||||
(set-response-status 400)
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
"(Error :message \"Missing actor_url\")")
|
||||
(let ((result (helper "pub-follow-remote" actor-url)))
|
||||
(do
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
(if (get result "error")
|
||||
(do
|
||||
(set-response-status 502)
|
||||
(str "(Error :message \"" (get result "error") "\")"))
|
||||
(str
|
||||
"(FollowSent"
|
||||
"\n :actor-url \"" (get result "actor-url") "\""
|
||||
"\n :status \"" (get result "status") "\")")))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Followers
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler pub-followers
|
||||
:path "/pub/followers"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((data (helper "pub-followers-data")))
|
||||
(do
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
(let ((items (map (fn (f)
|
||||
(str "\n (SxFollower"
|
||||
" :acct \"" (get f "acct") "\""
|
||||
" :actor-url \"" (get f "actor-url") "\")"))
|
||||
data)))
|
||||
(str "(SxFollowers" (join "" items) ")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Following
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler pub-following
|
||||
:path "/pub/following"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((data (helper "pub-following-data")))
|
||||
(do
|
||||
(set-response-header "Content-Type" "text/sx; charset=utf-8")
|
||||
(let ((items (map (fn (f)
|
||||
(str "\n (SxFollowing"
|
||||
" :actor-url \"" (get f "actor-url") "\")"))
|
||||
data)))
|
||||
(str "(SxFollowing" (join "" items) ")")))))
|
||||
|
||||
Reference in New Issue
Block a user