Prepend function to every route request in yada - clojure

currently i am playing with Yada as web lib. Now i want to execute some function before a route is hit.
Approaches I have tested:
- wrap the current resource as sub-resource, but then the swagger-doc doesn't find the resource
- using an prepend-interceptor, but the docu is not complete at this point at i got errors
My code:
(ns all-mighty.namespace
(:require [yada.yada :refer [handler listener resource as-resource]]
[yada.swagger :refer [swaggered]])
(defn resources []
[""
(swaggered
[""
[
(cool-route)
]]
{:info {:title "Hello You!"
:version "1.0"
:description "It's something"}
:basePath ""}
)])
(defn cool-route []
["/cool" (resource {
:description "Returns somethign cool"
:produces "application/json"
:methods {:get {:response cool-response}}}
)])
(defn cool-response [ctx]
(-> (:response ctx)
(assoc :status 200)
(assoc :body {:state :up}))
Yeah, I'll refactor the resources latter ;-)
Does someone has an idea?

The way I'm using append-interceptor:
(ns all-mighty.namespace
(:require
[yada.handler :refer [append-interceptor]]
[yada.interceptors :as i]
[yada.swagger :refer [swaggered]]
[yada.yada :refer [handler listener resource as-resource]]))
(defn cool-response [ctx]
{:state :up
:my/value (get ctx :my/value)})
(defn my-cool-interceptor [ctx]
(assoc ctx :my/value 10))
(defn my-cool-resource
[model]
(-> model
;; you have to provide an interception chain, here we use the default one
(assoc :interceptor-chain yada.yada/default-interceptor-chain)
resource
;; here we append our interceptor after the request body has been processed
(append-interceptor i/process-request-body my-cool-interceptor)))
(defn cool-route []
["/cool" (my-cool-resource {:description "Returns somethign cool"
:produces "application/json"
:methods {:get {:response cool-response}}})])
(defn routes []
[""
(swaggered
[""
[
(cool-route)
]]
{:info {:title "Hello You!"
:version "1.0"
:description "It's something"}
:basePath ""}
)])
(comment
(def l (listener (routes) {:port 1337}))
((:close l))
)
So for every resource under /cool you can use the my-cool-resource function to automatically add the desired interceptor.

Related

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}}

cljs + luminus framework:file upload with google closure

I follow the example code of book web development with clojure, 2nd edition and I have a problem with the file upload with google closure.
I test the file upload with Swagger and it response me 200 ok, I think the error is from the upload-file! function.(see below).
But I look up the closure api doc, it seems I use the correct function.
So I was in a trouble, I don't know why it doesn't work...
I need someone help.Here is my code(I use semantic-ui for ui components):
(defn upload-file! [upload-form-id status]
(reset! status nil)
(let [io (IframeIo.)]
(gev/listen
io goog.net.EventType.SUCCESS
#(reset! status [c/success-message "file uploaded successfully"]))
(gev/listen
io goog.net.EventType.ERROR
#(reset! status [c/warning-message "failed to upload the file"]))
(.setErrorChecker io #(= "error" (.getResponseText io)))
(.sendFromForm io (.getElementById js/document upload-form-id) "/upload")))
(defn upload-form []
(let [status (atom nil)
form-id "upload-form"]
(fn []
[c/modal
[:div "Upload File"]
[:div
(when #status #status)
[:div.ui.form
{:id form-id
:enc-type "multipart/form-data"
:method "POST"}
[:label {:for "file"} "select an image for upload"]
[:input {:id "file"
:name "file"
:type "file"}]]]
[:div
[:button.ui.primary.button
{:on-click #(upload-file! form-id status)}
"upload"]
[:button.ui.red.button
{:on-click #(do
(.modal (js/$ ".ui.modal") "hide")
(reset! status nil))}
"Cancel"]]
"upload"])))
the components:
(defn modal [header content footer id]
[:div.ui.modal
{:id id}
[:div.header header]
[:div.content content]
[:div.actions footer]])
(defn success-message [content]
[:div.ui.green.message
[:div.header content]])
So I solved my question, I should type [:form:ui.form] rather than [:div.ui.form].
if you interest in the code, you can view this URL:
upload image code

Clojure - OpenID - Luminus - Steam Integration

I am new to Clojure and using Luminus to build a website, I have been trying to integrate OpenID to my site but I am failing so bad. I have this code example:
https://github.com/cemerick/friend-demo/blob/master/src/clj/cemerick/friend_demo/openid.clj
demo: http://friend-demo.herokuapp.com/openid/
I am trying to implement it to my site but I keep getting errors, in the end I just wanted to copy it exactly to see if its working on my localhost. So I have this code in my home.clj
But it just doesn't work, whenever I click Login button I get "Not Found" at localhost:3000/login
Does it mean I need to handle /login somewhat? but it is not documented in the example code above.
(def providers [{:name "Steam" :url "http://steamcommunity.com/openid"}
{:name "Yahoo" :url "http://me.yahoo.com/"}])
(defroutes home-routes
(GET "/" req
(h/html5
pretty-head
(pretty-body
; (github-link req)
[:h2 "Authenticating with various services using OpenID"]
[:h3 "Current Status " [:small "(this will change when you log in/out)"]]
(if-let [auth (friend/current-authentication req)]
[:p "Some information delivered by your OpenID provider:"
[:ul (for [[k v] auth
:let [[k v] (if (= :identity k)
["Your OpenID identity" (str (subs v 0 (* (count v) 2/3)) "…")]
[k v])]]
[:li [:strong (str (name k) ": ")] v])]]
[:div
[:h3 "Login with…"]
(for [{:keys [name url]} providers
:let [base-login-url (context-uri req (str "/login?identifier=" url))
dom-id (str (gensym))]]
[:form {:method "POST" :action (context-uri req "login")
:onsubmit (when (.contains ^String url "username")
(format "var input = document.getElementById(%s); input.value = input.value.replace('username', prompt('What is your %s username?')); return true;"
(str \' dom-id \') name))}
[:input {:type "hidden" :name "identifier" :value url :id dom-id}]
[:input {:type "submit" :class "button" :value name}]])
[:p "…or, with a user-provided OpenID URL:"]
[:form {:method "POST" :action (context-uri req "login")}
[:input {:type "text" :name "identifier" :style "width:250px;"}]
[:input {:type "submit" :class "button" :value "Login"}]]])
[:h3 "Logging out"]
[:p [:a {:href (context-uri req "logout")} "Click here to log out"] "."])))
(GET "/logout" req
(friend/logout* (resp/redirect (str (:context req) "/")))))
(def page (friend/authenticate
home-routes
{:allow-anon? true
:default-landing-uri "/"
:workflows [(openid/workflow
:openid-uri "/login"
:credential-fn identity)]}))
The /login callback is handled by openid-workflow which essentially becomes a wrapper around your home-routes. You are likely using home-routes as your request handler when you should use page instead.

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)))

Compojure: how to map query parameters

I'm trying to make any of the following mappings work to map http://mysite.org/add?http://sitetoadd.com or http://mysite.org/add?u=http://sitetoadd.com
(GET "/add?:url" [url] url)
(GET "/add?u=:url" [url] url)
(GET "/add" {params :params} (params :u))
(GET "/add" {params :params} (params "u"))
(GET "/add" [u] u)
But it just fails and I don't know why. On the other hand, this works:
(GET "/add/:url" [url] url)
but I can't use it because I have to pass a url and http://mysite.org/add/http://sitetoadd.com is invalid while http://mysite.org/add?http://sitetoadd.com is ok.
EDIT: dumping request i've seen that params is empty. I thought that it would contain POST and GET parameters, but the only place where I can find the params I pass is in :query-string ("u=asd"). It seems that a middleware is needed to parse query strings. My question stands still, by the way.
See https://github.com/weavejester/compojure under Breaking Changes. The params map is no longer bound by default. If you have your example routes inside a "(defroutes main-routes ... )", make sure you activate it through "(handler/site main-routes)" as explained on https://github.com/weavejester/compojure/wiki/Getting-Started as it is the site or api method that makes sure the params map gets bound by default.
Here's an example that works:
(ns hello-world
(:use compojure.core, ring.adapter.jetty)
(:require [compojure.route :as route]
[compojure.handler :as handler]))
(defroutes main-routes
(GET "/" {params :params} (str "<h1>Hello World</h1>" (pr-str params)))
(route/not-found "<h1>Page not found</h1>"))
(defn -main [& m]
(run-jetty (handler/site main-routes) {:port 8080}))