diff options
| author | eltrac <hi@eltr.ac> | 2026-10-07 17:04:04 +0800 |
|---|---|---|
| committer | eltrac <hi@eltr.ac> | 2026-10-07 17:04:04 +0800 |
| commit | e6ca4cbecff78a0f26086178c48e8fc66914ab86 (patch) | |
| tree | 489bd51e684a3972734aa5b79efef8413dc307bf | |
| parent | 89d5bd3a26619e30dcf8b7382432180a399d25c9 (diff) | |
handler: pluggable services in API routes/handlers
| -rw-r--r-- | src/weepinbell/webserver/facade.clj | 22 | ||||
| -rw-r--r-- | src/weepinbell/webserver/feed.clj | 2 | ||||
| -rw-r--r-- | src/weepinbell/webserver/handler.clj | 43 | ||||
| -rw-r--r-- | src/weepinbell/webserver/receiver.clj | 3 | ||||
| -rw-r--r-- | src/weepinbell/webserver/retriever.clj | 3 | ||||
| -rw-r--r-- | test/weepinbell/handler_test.clj | 29 |
6 files changed, 65 insertions, 37 deletions
diff --git a/src/weepinbell/webserver/facade.clj b/src/weepinbell/webserver/facade.clj index ba9b8b0..23d5689 100644 --- a/src/weepinbell/webserver/facade.clj +++ b/src/weepinbell/webserver/facade.clj @@ -1,19 +1,33 @@ (ns weepinbell.webserver.facade (:require [org.httpkit.server :refer [run-server]] - [weepinbell.webserver.handler :refer [app]] [taoensso.timbre :as timbre] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [weepinbell.webserver.handler :refer [app]] + ;; services + [weepinbell.webserver.receiver :as receiver] + [weepinbell.webserver.retriever :as retriever] + [weepinbell.webserver.feed :as feed])) (s/def ::server-meta (s/keys :req-un [::wmc ::store ::config])) +(defn make-services [] + {:post [#(s/valid? :weepinbell.webserver.handler/services %)]} + {:gen-feed feed/rss + :receive receiver/receive + :retrieve-by-domain retriever/by-domain + :retrieve-by-url retriever/by-url + :retrieve-by-id retriever/by-id + :retrieve-by-pair retriever/by-st-pair}) + (defn start-webserver "Starts web server. Returns a function that stops the server." [metadata] {:pre [(s/valid? ::server-meta metadata)] :post [fn?]} - (let [{:keys [wmc store config]} metadata + (let [{:keys [config]} metadata + api-meta (assoc metadata :services (make-services)) port (Integer/parseInt (or (:port config) (System/getenv "PORT") "3000"))] (timbre/info "starting web server, listening on" port) - (run-server (app wmc store config) {:port port}))) + (run-server (app api-meta) {:port port}))) diff --git a/src/weepinbell/webserver/feed.clj b/src/weepinbell/webserver/feed.clj index 12a39ec..7a78a9a 100644 --- a/src/weepinbell/webserver/feed.clj +++ b/src/weepinbell/webserver/feed.clj @@ -1,5 +1,5 @@ (ns weepinbell.webserver.feed - "Handler of route /feed/:domain. Generating RSS feed for each domain." + "Service generating RSS feed for each domain." (:require [clj-rss.core :as rss] [weepinbell.utils.url :refer [get-domain]] [weepinbell.utils.digest :refer [digest]]) diff --git a/src/weepinbell/webserver/handler.clj b/src/weepinbell/webserver/handler.clj index b6fac9e..b1d6990 100644 --- a/src/weepinbell/webserver/handler.clj +++ b/src/weepinbell/webserver/handler.clj @@ -1,20 +1,31 @@ (ns weepinbell.webserver.handler "Handler define routes and wrap responses. It pass request to other specific handlers. Other handlers are receiver, retriever and feed." - (:require [compojure.core :refer [routes POST GET context]] + (:require [clojure.spec.alpha :as s] + [compojure.core :refer [routes POST GET context]] [compojure.route :as route] [ring.middleware.defaults :refer [wrap-defaults api-defaults]] [ring.middleware.resource :refer [wrap-resource]] - [ring.middleware.json :refer [wrap-json-response]] - [weepinbell.webserver.receiver :refer [receive]] - [weepinbell.webserver.retriever :as retriever] - [weepinbell.webserver.feed :as feed])) + [ring.middleware.json :refer [wrap-json-response]])) + +(s/def ::services + (s/and (s/keys :req-un [::receive ::retrieve-by-url ::retrieve-by-domain + ::retrieve-by-id ::retrieve-by-pair ::gen-feed]) + #(every? fn? (vals %)))) +(s/def ::api-meta + (s/keys :req-un [::wmc ::store ::config ::services])) (defn- make-api "Receives webmention channel, storage vtable and config map. Returns a vector of compojure route handlers. Some may be nil." - [wmc storage config] - [;; receiver endpoint + [api-meta] + {:pre [(s/valid? ::api-meta api-meta)] + :post [sequential?]} + (let [{:keys [wmc store config services]} api-meta + {:keys [receive retrieve-by-url retrieve-by-domain + retrieve-by-id retrieve-by-pair + gen-feed]} services] + [;; receiver endpoint (POST (:endpoint config) [source target] (receive {:s source :t target} wmc config)) ;; query API routes @@ -22,21 +33,21 @@ ;; query webmention list (context "/mentions" [page microformats mftype] (GET "/" [url] - (retriever/by-url url page storage + (retrieve-by-url url page store {:microformats microformats :type mftype})) (GET "/:domain" [domain] - (retriever/by-domain domain page storage + (retrieve-by-domain domain page store {:microformats microformats :type mftype}))) ;; query one specific webmention (context "/mention" [microformats] - (GET "/:id" [id] (retriever/by-id id storage + (GET "/:id" [id] (retrieve-by-id id store {:microformats microformats})) - (GET "/" [source target] (retriever/by-st-pair source target storage + (GET "/" [source target] (retrieve-by-pair source target store {:microformats microformats})))) ;; RSS feed - (GET "/feed/:domain" [domain] (feed/rss domain storage config)) + (GET "/feed/:domain" [domain] (gen-feed domain store config)) ;; host proxied avatars (microformats photos) (route/files "/avatars/" {:root "data/avatars"}) ;; frontend @@ -44,7 +55,7 @@ (when (= (:source fe) :disk) (route/files "/" {:root (:dir fe)}))) ;; 404 - (route/not-found "Not Found")]) + (route/not-found "Not Found")])) (defn- make-handler "Receive a vector of handlers. Remove nil elements. @@ -55,11 +66,11 @@ (defn app "Receive webmention channel, storage vtable and config map. Returns an app handler." - [wmc storage config] - (when (= :clojure.spec.alpha/invalid storage) + [api-meta] + (when (= :clojure.spec.alpha/invalid (:store api-meta)) (println "Invalid storage. Check your database configuration or submit an issue.") (System/exit 1)) - (-> (make-api wmc storage config) + (-> (make-api api-meta) make-handler (wrap-resource "static") (wrap-defaults api-defaults) diff --git a/src/weepinbell/webserver/receiver.clj b/src/weepinbell/webserver/receiver.clj index 144f3db..9d14a10 100644 --- a/src/weepinbell/webserver/receiver.clj +++ b/src/weepinbell/webserver/receiver.clj @@ -1,6 +1,5 @@ (ns weepinbell.webserver.receiver - "Handler of route /endpoint or any other route the user configured. - Receiver receives Webmention request (a source and a target); verify the request; return error message if any; push Webmention into webbmention channel for processor to take." + "Service receiving Webmention request (a source and a target); verify the request; return error message if any; push Webmention into webbmention channel for processor to take." (:require [clojure.core.async :refer [put!]] [weepinbell.utils.url :refer [get-domain valid-http-url? normalize url=]] [weepinbell.utils.digest :refer [digest]])) diff --git a/src/weepinbell/webserver/retriever.clj b/src/weepinbell/webserver/retriever.clj index a8c175d..b597daf 100644 --- a/src/weepinbell/webserver/retriever.clj +++ b/src/weepinbell/webserver/retriever.clj @@ -1,6 +1,5 @@ (ns weepinbell.webserver.retriever - "Handler of routes /api/mention/:id, /api/mentions/... - Retriever gets webmention form storage and wraps it in a response." + "Service getting webmention form storage and wrapping it in a response." (:require [weepinbell.utils.url :refer [get-domain url=]] [weepinbell.microformats.reducer :refer [reduce-mf]])) diff --git a/test/weepinbell/handler_test.clj b/test/weepinbell/handler_test.clj index a59dd5e..0e46a22 100644 --- a/test/weepinbell/handler_test.clj +++ b/test/weepinbell/handler_test.clj @@ -1,12 +1,17 @@ (ns weepinbell.handler-test (:require [clojure.test :refer [testing is deftest]] [ring.mock.request :as mock] - [weepinbell.handler :refer [app]] + [weepinbell.webserver.handler :refer [app]] + [weepinbell.webserver.facade :refer [make-services]] [cheshire.core :refer [parse-string]])) +(defn test-app [wmc store config] + (app {:wmc wmc :store store :config config + :services (make-services)})) + (deftest api-routes-test (testing "/api/mentions/:domain route" - (let [response ((app nil {:get-by-domain (fn [_] + (let [response ((test-app nil {:get-by-domain (fn [_] [{:webmention {:source "" :target "" :id "" @@ -17,7 +22,7 @@ (is (some? (:body response))))) (testing "/api/mentions?url= route" - (let [response ((app nil + (let [response ((test-app nil {:get-by-domain (fn [_] [{:webmention {:source "https://source.net" @@ -33,7 +38,7 @@ (is (-> response :body parse-string first (get "webmention") some?))))) (testing "/api/mention/:id" - (let [response ((app nil {:get (fn [_] + (let [response ((test-app nil {:get (fn [_] {:webmention {:source "" :target "" :id "xxx" @@ -43,7 +48,7 @@ (is (= (:status response) 200)))) (testing "/api/mention/?source=&target=" - (let [response ((app nil {:get (fn [_ _] + (let [response ((test-app nil {:get (fn [_ _] {:webmention {:source "1" :target "2" :id "xxx" @@ -54,34 +59,34 @@ (deftest feed-route-test (testing "RSS feed for allowed domains and has items" - (let [response ((app nil {:get-by-domain (fn [_] [{:webmention nil}])} + (let [response ((test-app nil {:get-by-domain (fn [_] [{:webmention nil}])} {:allowed-target-domains "valid.com"}) (mock/request :get "/feed/valid.com"))] (is (= (:status response) 404)))) (testing "RSS feed for allowed domains, but with no webmentions" - (let [response ((app nil nil {:allowed-target-domains "valid.com"}) + (let [response ((test-app nil nil {:allowed-target-domains "valid.com"}) (mock/request :get "/feed/valid.com"))] (is (= (:status response) 404)))) (testing "not found for disallowed domains" - (let [response ((app nil nil {}) (mock/request :get "/feed/invalid.com"))] + (let [response ((test-app nil nil {}) (mock/request :get "/feed/invalid.com"))] (is (= (:status response) 404))))) (deftest not-found-route-test (testing "not-found route" - (let [response ((app nil nil {}) (mock/request :get "/invalid"))] + (let [response ((test-app nil nil {}) (mock/request :get "/invalid"))] (is (= (:status response) 404)))) (testing "root route is not found when frontend is disabled" - (let [response ((app nil nil {:frontend nil}) (mock/request :get "/"))] + (let [response ((test-app nil nil {:frontend nil}) (mock/request :get "/"))] (is (= (:status response) 404))))) (deftest frontend-route-test (testing "serve frontend resources from disk when enabled" (let [dir (System/getProperty "java.io.tmpdir") - response ((app nil nil {:frontend {:source :disk :dir dir}}) + response ((test-app nil nil {:frontend {:source :disk :dir dir}}) (mock/request :get "/"))] (is (not= (:status response) 500)))) (testing "serve nothing when disabled" - (let [response ((app nil nil {:frontend nil}) + (let [response ((test-app nil nil {:frontend nil}) (mock/request :get "/"))] (is (= (:status response) 404))))) |
