cljs + luminus framework:file upload with google closure - clojure

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

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

Prepend function to every route request in yada

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.

How to get logged in user's id from session in LuminusWeb

I've just saved the current logged-in user in session. Now how do I get user's id from session in Selmer templates?. Thanks
(defn login-form [{:keys [flash]}]
(layout/render
"login_form.html"
(merge (select-keys flash [:id :pass :errors]))))
(defn authenticate [id pass]
(when-let [user (db/get-user {:id id})]
(when (hashers/check pass (:pass user))
id)))
(defn login! [{:keys [params]}]
(if-let [id (authenticate (:id params) (:pass params))] ;TODO: Refactor
(do
>>>>>>>>> (update (response/found "/") :session {:id id}))
(layout/render "login_form.html" {:invalid-credentials? true})))
(defroutes auth-routes
(GET "/accounts/login/" request (login-form request))
(POST "/accounts/login/" request (login! request)))
Update
I've updated my login function. and it works.
(defn login! [{:keys [params session]}]
(if-let [id (authenticate (:id params) (:pass params))] ;TODO: Refactor
(-> (response/found "/")
(assoc :session
(assoc session :id id)))
(layout/render "login_form.html" {:invalid-credentials? true})))
To print the user's id, I've changed the home-routes.
(defn home-page [opts]
(layout/render "home.html" opts))
(defroutes home-routes
(GET "/" {:keys [flash session]} (home-page (or flash session))))
Now I can print user's id in home.html template. But If I use any other url then user's id stopped `displaying``.
Question > So do I need to pass {:keys [flash session]} in every routes?
You can make use of the wrap-identity middleware which Luminus provides by making sure it's in your wrap-base stack, then change :id to :identity in your session. You can now call (layout/render "home.html") (even without providing opts!) and you can access the identity using {{identity}}

How to mock test POST requests with body as JSON using ring mock request?

I am using http-kit as the server with wrap-json-body from ring.middleware.json to get the stringified JSON content sent from the client as the request body. My core.clj is:
; core.clj
; ..
(defroutes app-routes
(POST "/sign" {body :body} (sign body)))
(def app (site #'app-routes))
(defn -main []
(-> app
(wrap-reload)
(wrap-json-body {:keywords? true :bigdecimals? true})
(run-server {:port 8080}))
(println "Server started."))
When I run the server using lein run the method works correctly. I am stringifying the JSON and sending it from the client. The sign method gets the json correctly as {"abc": 1}.
The problem is when during mock test. The sign method gets a ByteArrayInputStream and I am using json/generate-string to convert to string which fails in this case. I tried wrapping the handler in wrap-json-body but it is not work. Here are my test cases I tried out core_test.clj:
; core_test.clj
; ..
(deftest create-sign-test
(testing "POST sign"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test1
(testing "POST sign1"
(let [response (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test2
(testing "POST sign2"
(let [response (core/app (-> (mock/body (mock/request :post "/sign")
(json/generate-string {:user 1}))
(mock/content-type "application/json")))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test3
(testing "POST sign3"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" {:headers {"content-type" "application/json"}
:body "{\"foo\": \"bar\"}"}))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
All of the fails with the following error:
Uncaught exception, not in assertion.
expected: nil
actual: com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.ByteArrayInputStream: java.io.ByteArrayInputStream#4db77402
How can I pass a JSON string as the body to the method in ring mock test?
There are three issues in your code.
Your test doesn't wrap your app handler in wrap-json-body so it might not get correctly parsed request body in your handler. You need to first wrap your app in wrap-json-body and then call it with your mock request. (You could also have your app handler to be already wrapped instead of wrapping it both in your main function and tests)
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true})]
(handler your-mock-request))
Your mock request doesn't include proper content type and your wrap-json-body won't parse your request body to JSON. That's why your sign function gets ByteArrayInputStream instead of parsed JSON. You need to add content type to your mock request:
(let [request (-> (mock/request :post "/sign" "{\"username\": \"jane\"}")
(mock/content-type "application/json"))]
(handler request))
Verify that your sign function returns a response map with JSON as string in body. If it creates response body as input stream you need to parse it in your test function. Below I am using cheshire to parse it (converting JSON keys to keywords):
(cheshire.core/parse-stream (-> response :body clojure.java.io/reader) keyword)
Additionally instead of writing your JSON request body by hand you can use Cheshire to encode your data into JSON string:
(let [json-body (cheshire.core/generate-string {:username "jane"})]
...)
With those changes it should work correctly like in my slightly modified example:
(defroutes app-routes
(POST "/echo" {body :body}
{:status 200 :body body}))
(def app (site #'app-routes))
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true}))
json-body (json/generate-string {:username "jane"})
request (-> (mock/request :post "/echo" json-body)
(mock/content-type "application/json"))
response (handler request)]
(is (= (:status response) 200))
(is (= (:body response) {:username "jane"})))

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.