How to extract a path-param from a Reitit backend end route - clojure

How do you get a path-param out of a Reitit backend Clojure route? I am trying to get the val associated with :id in the following manner, but keep getting a 404 file not found error in the REPL.
["/foo/:id" {:get
(fn [{:keys [path-params ]}]
(some-ns/some-fn (:id path-params)))}]
I have tried using the documentation at https://luminusweb.com/docs/routes.html and https://github.com/metosin/reitit/blob/master/README.md.

Destructure the path params from the request, and then extract the id.
(defn project-routes
"SPA routing"
[]
["" {:middleware [middleware/wrap-base
middleware/wrap-formats]}
["/foo"
["/bar/:id"
{:get (fn [{:keys [path-params] :as _req}]
(http-response/ok
(core/READ some-table
(Integer/valueOf (:id path-params)))))}]]])
Documentation and examples obtained from https://github.com/luminus-framework/luminus-docs/blob/master/resources/md/sessions_cookies.md. Controllers were used, as documented by https://clojureverse.org/t/how-do-you-accomplish-spa-re-frame-teardown/5516/6.

Related

How does one simply write an interceptor that extracts json from a get/post request using Pedestal

I am making a simple API That will require to read body parameters from a json/edn request
I am trying to get the program to echo the contents as edn objects but something seems to not work here is my routes
(def routes
(route/expand-routes
#{["/echo" :get [body-params/body-params print-response] :route-name :greet]}))
The interceptor
(def print-response {:name ::print-response
:leave (fn [context]
(let [content-type (get-in context [:request :content-type])
updated-response (assoc (-> context :response) :headers {"Content-Type" content-type}
:status 200
:body (get-in context [:request] :edn-params))]
(assoc context :response updated-response))
)
}
)
fix : put parantheses around body-params
explanation
actually body-params is a higher order function that creates an interceptor so it must be called
(def routes
(route/expand-routes
#{["/echo" :get [(body-params/body-params) print-response] :route-name :greet]}))

Can't see Swagger service endpoints

I am trying to access my Swagger service endpoints. Here is the code:
(defn service-routes []
["/api"
{:middleware [middleware/wrap-formats]
:swagger {:id ::api}}
["" {:no-doc true}
["/swagger.json"
{:get (swagger/create-swagger-handler)}]
["/swagger-ui*"
{:get (swagger-ui/create-swagger-ui-handler
{:url "api/swagger.json"})}]]
["/business-partners"
{:get
(fn [_]
(response/ok (bp/business-partners-list)))}]
["/business-partner"
{:post
(fn [{:keys [params]}]
(try
(bp/save-business-partner params)
(response/ok {:status :ok})
(catch Exception e
(let [{id :business-partners/error-id
errors :errors} (ex-data e)]
(case id
:validation
(response/bad-request {:errors errors})
(response/internal-server-error
{:errors {:server-error ["Failed to save message!"]}}))))))}]])
But when I tried to access http://localhost:3000/api/swagger.json in browser I got this response:
{"swagger":"2.0",
"x-id":["businesspartners.routes.services/api"],
"paths":{
"/api/business-partners": {"get": {}},
"/api/business-partner": {"post": {}}}}
I can't figure out why did I get this in json format and why not see Swagger UI to visaulize and interact with my services? I should get something like this:
I’d imagine you’ll find the swagger ui under /api
I was also facing same issue and using code generation with Java with language jaxrs
Once you generate the code you will see the index.html page generated with endpoints under target/generated-sources/swagger/ folder. Make sure you include it in you build and point api path to this page so that you can view endpoints as html.

Reitit ring. Authentication and authorization in only one set of routes on Luminus

I'm developing a site with Luminus, until now my middleware wrap-base function looks like:
(defn wrap-base [handler]
(-> ((:middleware defaults) handler)
wrap-auth
(wrap-access-rules {:rules rules :on-error on-error})
(wrap-authentication (session-backend))
wrap-flash
(wrap-defaults
(-> site-defaults
(assoc-in [:security :anti-forgery] false)
(assoc-in [:session :store] (ttl-memory-store (* 60 30)))))
wrap-internal-error))
and my routes/home.clj file:
(defn home-routes []
[""
{:middleware [middleware/wrap-csrf
middleware/wrap-formats]}
(merge public-routes admin-routes)])
but now I need to develop a new API ("/api/getcustomers") so all the authentication/authorization (and csrf) middleware must be only for the "home-routes" and not for the new API routes. The API routes are saved in a new routes/services.clj file.
Happily Luminus uses reitit.ring, a data-driven routing solution, but I'm not sure how to move the authentication/authorization stuff out of the general middleware and assign it only for "home-routes" section.
At the end, I created a rule for the new API in ring:
;; File: src/some_app/middleware.clj
(defn open-gates [request]
true)
(def rules [{:pattern #"^/admin.*"
:handler admin-access
:redirect "/notauthorized"},
{:pattern #"^\/vclass.*"
:handler user-access
:redirect "/notauthorized"},
{:pattern #"^\/api.*"
:handler open-gates
:redirect "/notauthorized"},
{:pattern #"^/user.*"
:handler authenticated?}])

How do I make middleware to put a response header only when `:swagger {:deprecated true}`?

We use the compojure-api to get us some nice swagger integration in our ring apps. The :swagger {:deprecated true} meta works like a champ to get the swagger page correct, but I have a requirement that I put a specific header on the response when the route is :swagger {:deprecated true}. I am struggling to figure out how to do this with the middleware pattern that I've been using to do similar response header manipulations.
(ns bob.routes
(:require [clojure.tools.logging :as log]
[compojure.api.sweet :refer :all]
[ring.util.http-response :as status]
[schema.core :as s]
[ring.swagger.schema :as rs]))
(s/defschema BobResponse {:message (rs/describe String "Message")})
(defn wrap-bob-response-header [handler]
(fn [request]
(let [response (handler request)]
;; can I reach into the request or the response to see what
;; route served this and if it has the :swagger {:deprecated true}
;; meta on it and NOT emit the x-bob header if it does?
(assoc-in response [:headers "x-bob"] "Robert"))))
(defroutes bob-routes
(context "" []
:middleware [wrap-bob-response-header]
:tags ["bob"]
:description ["Tease out how to do swagger driven response header"]
(GET "/notdeprectated" [:as request]
:swagger {:deprecated false}
:new-relic-name "GET_notdeprecated"
:return BobResponse
(status/ok {:message "All is well"}))
(GET "/isdeprecated" [:as request]
:swagger {:deprecated true}
:new-relic-name "GET_isdeprecated"
:return BobResponse
(status/ok {:message "You came to the wrong neighborhood."}))))
How do I modify wrap-bob-response-header to only emit x-bob on routes with :swagger {:deprecated true}?
With Compojure-API, the middleware are invoked in-place, at the path context they are defined at. In your example, the wrap-bob-response-header doesn't yet know where the request is going to go (or will it even match anything). If it knew, you could use the injected route information from the request (see https://github.com/metosin/compojure-api/blob/master/src/compojure/api/api.clj#L71-L73) to determine if the endpoints would have the swagger information set.
What you could do, is mount the header-setting middleware only to the routes that need it.
There is a library called reitit (also by Metosin) which solves this by applying a route-first architecture: the full path lookup is done first and the middleware chain is applied after that. Because of this, all the middleware know the endpoint they are mounted to. Middleware can just query the endpoint data (either at request-time or at compile-time) and act accordingly. They can even decide not to mount to that spesific route.
Reitit is feature-par with compojure-api, just with different syntax, e.g. fully data-driven.
Good examples in the blog: https://www.metosin.fi/blog/reitit-ring/
PS. I'm co-author of both of the libs.
EDIT.
Solution to inject data to the response after a match:
1) create middleware that adds data (or meta-data) to the response
2) add or modify a restructuring handler to mount the middleware from 1 into the endpoint, with the given data (available in the handler)
3) read the data in the response pipeline and act accordingly
(defn wrap-add-response-data [handler data]
(let [with-data #(assoc % ::data data)]
(fn
([request]
(with-data (handler request)))
([request respond raise]
(handler #(respond (with-data %)) raise)))))
(defmethod compojure.api.meta/restructure-param :swagger [_ swagger acc]
(-> acc
(assoc-in [:info :public :swagger] swagger)
(update-in [:middleware] into `[[wrap-add-response-data ~swagger]])))
(def app
(api
(context "/api" []
(GET "/:kikka" []
:swagger {:deprecated? true}
(ok "jeah")))))
(app {:request-method :get, :uri "/api/kukka"})
; {:status 200, :headers {}, :body "jeah", ::data {:deprecated? true}}

Why does my Ring middleware not see the :params map in the request?

I'm writing a Ring middleware, and also using Compojure. I want my middleware to look in the :params map to see if a specific key was provided by the user. In my middleware function, the request map does not contain a :params map, though. In the final request handler, there is a :params map. I'm thinking the :params map it not being set before my custom middleware, but I can't figure out how to get it to actually be set.
Any ideas?
(ns localshop.handler
(:use [ring.middleware.format-response :only [wrap-restful-response]]
[compojure.core])
(:require [localshop.routes.api.items :as routes-api-items]
[localshop.middleware.authorization :as authorization]
[compojure.handler :as handler]))
;; map the route handlers
(defroutes app-routes
(context "/api/item" [] routes-api-items/routes))
;; define the ring application
(def app
(-> (handler/api app-routes)
(authorization/require-access-token)
(wrap-restful-response)))
Above is my handler.clj file, and below is the middleware itself.
(ns localshop.middleware.authorization)
(defn require-access-token [handler]
(fn [request]
(if (get-in request [:params :token])
(handler request)
{:status 403 :body "No access token provided"})))
I actually figured this out. This works if you adjust the (def app ... ) portion of the code so that it matches the following:
(def app
(-> app-routes
(wrap-restful-response)
(authorization/require-access-token)
(handler/api)))