Request map passed to my anonymous function inside a Compojure handler - clojure

I'm two days into learning Clojure by writing a simple REST server using ring-clojure and Compojure with ring-json's wrap-json-body middleware.
So far, I have:
A vector users containing the users (with a couple of default users):
(def users [{:id 0 :username "aname"}
{:id 1 :username "anothername"}])
A function (form?) save-user that accepts a map (user) and looks for existing users with the same username. If the username is available, I overwrite the users vector to include the new user before returning HTTP 201. If the username is taken, I simply return HTTP 400:
(defn save-user [user]
(prn users)
(if
(not-any? #(= (:username %) (:username user)) users)
(fn [request]
(def users (conj users user))
(status
(response (str "Saved user with username: " (:username user)))
201))
(status
(response (str "User with username '" (:username user) "' already exists"))
400)))
A route for POST /users which calls save-user with the received map:
(defroutes app-routes
(POST "/users" request (save-user (:body request))))
I don't think it matters, but the middleware is applied like this:
(def app
(-> app-routes
(wrap-cors :access-control-allow-origin "*" :access-control-allow-methods "*")
(wrap-json-response)
(wrap-keyword-params)
(wrap-json-body {:keywords? true :bigdecimals? true})
(wrap-defaults (assoc site-defaults :security false))))
My problem:
For whatever reason, the entire request map is passed to the function i pass as then inside the if. Printing it:
(if
(not-any? #(= (:username %) (:username user)) users)
(fn [request]
(prn request))
...
... gives me this:
{:ssl-client-cert nil, :cookies {}, :remote-addr "0:0:0:0:0:0:0:1", :params {}, :flash nil, :route-params {}, :headers {"host" "localhost:3000", "accept" "*/*", "content-length" "42", "content-type" "application/json", "user-agent" "curl/7.43.0"}, :server-port 3000, :content-length 42, :form-params {}, :compojure/route [:post "/users"], :session/key nil, :query-params {}, :content-type "application/json", :character-encoding nil, :uri "/users", :server-name "localhost", :query-string nil, :body {:username "testusername", :password "testpassword"}, :multipart-params {}, :scheme :http, :request-method :post, :session {}}
The same happens if I pass an anonymous function as the if's else. However, nothing wrong happens when I only pass (status ...), like in the code above.
From what I understand, the request map shouldn't be available inside save-user at all, since it's not passed as an argument. Why is it passed to my anonymous function, and is there any way to simply ignore it?

In save-user you are returning a function that takes a request and returns a response (a handler).
I suppose you should instead just return the response directly. Just replace (fn [request] with (do to wrap multiple expressions in a single form.
(def users (atom [{:id 0 :username "aname"}
{:id 1 :username "anothername"}]))
(defn save-user [user]
(if (not-any? #(= (:username %) (:username user)) #users)
(do
(swap! users conj user)
(status
(response (str "Saved user with username: " (:username user)))
201))
(status
(response (str "User with username '" (:username user) "' already exists"))
400)))
I’ve also changed the global users var to be an atom. Redefining global vars from inside a function is a big no-no in Clojure.

Related

Clojure / Ring: Response map is nil when adding session key

I want to update a session key on /callback upon logging in. Here's the handler:
(defn callback [{session :session :as request}]
(let [id (:id (get-current-users-profile))
updated-session (assoc session :identity id)]
{:status 200
:headers {"Content-Type" "text/html"}
:body (loading-page)
:session updated-session}))
As long as the session key-value pair is present in the response map, I get a 500 "Response map is nil" response.
Is there something I'm doing wrongly here?
By the way, I can still update the session map (with no errors) on my /logout route by doing the following:
(defn logout [{session :session}]
(-> (resp/redirect "/")
(assoc :session {})))
And here's what calls both callback and logout:
(defroutes main-routes
(GET "/" [] index)
(GET "/login" [] login)
(GET "/callback" [] callback)
(GET "/logout" [] logout))

Compojure-api not respecting body from ring.mock.requests

I'm trying to use the ring.mock.requests library to test an http service. I have this bit of code
(auth-routes
(->
(mock/request :post "/auth/user")
(mock/body {:username "user" :password "pass"})
(mock/content-type "application/json")))
The inner part with the thread-first macro seems to work correctly, but it fails when I try to make the mock request to auth-routes
And this is my route:
(def auth-routes
(context "/auth" []
:tags ["Authentication"]
(POST "/user" []
:return s/Uuid
:body [request m/User]
:summary "Create new user"
(ok (auth/create-user request)))) ...)
This is my schema:
(def User {:username s/Str
:password s/Str})
The exception I'm seeing is
clojure.lang.ExceptionInfo
Request validation failed
{:error (not (map? nil)), :type :compojure.api.exception/request-validation}
It looks like my route is getting nil as the body, and I expect {:username "user" :password "pass"} to be there.
What am I doing wrong and how do I pass a body to this route?
I think you should serialize your data as a json string in your test. Using cheshire:
(cheshire.core/generate-string {:username "user" :password "pass"})
Take a look here or here.

how to load another page in cljs after post request using cljs-ajax

I have created a login page login.cljs and handler.clj.here is my code
login.cljs:-
(defn handler [response]
(let[status (aget (clj->js response) "status")
msg (str (aget (clj->js response) "greeting"))]
(if (= status 200)
(home/home-root)
(js/alert "Invalid username or password"))
))
(defn handle-submit [e app owner]
(let [[user user-node] (value-from-node owner "username")
[pass pass-node] (value-from-node owner "password")]
(POST "/hello"
{:params {:username user :password pass}
:handler handler
:error-handler error-handler
:format :json
:response-format :json})
))
(defn login-view [app owner]
(reify
om/IRender(render [this]
(dom/div nil
(dom/h1 nil "Login")
(dom/input #js {:type "text" :placeholder "Your Name" :ref "username"})
(dom/input #js {:type "password" :placeholder "Your Password" :ref "password"})
(dom/button
#js { :onClick (fn [event] (handle-submit event app owner))}
"submit")
))))
(defn say-hello []
(om/root
{} login-view (.getElementById js/document "content")))
home.cljs:-
(defn home-view [app owner]
(reify
om/IRender
(render [this]
(dom/div nil "Welcome"
))))
(defn home-root []
(om/root
{}
home-view
(.getElementById js/document "content"))
handler.clj:-
(defn login-check! [request]
(let [user (get-in request [:params :username])
pass (get-in request [:params :password])]
(cond
(and(= user "admin")(= pass "admin")) (response/response {:greeting user
:status 200})
:else (response/response {:greeting "Invalid username or password"
:status 403}))
))
(defroutes app-routes
(GET "/" [] (response/resource-response "index.html" {:root "public"}))
(route/resources "/")
(POST "/hello" request (login-check! request))
(GET "/home" request (response/response {:body "home"}))
(route/not-found "Not Found"))
I need to load home page view when its a valid login.
here i have called another cljs method 'home/home-root' but its not working.
Here i got one solution..I don't know how much its correct but its working good
I have created a new defn page using reagent as follows:-
(defn page [page-component]
(reagent/render-component [page-component]
(.getElementById js/document "content")))
and called this function from login.cljs handler method:
(defn handler [response]
(page home-root))
now the page method will be called and home-root cljs will be passed to it.

How does one configure sibling Pedestal routes so that all are accessible when using :constraints?

I am creating a toy Pedestal service intended to have the following resources:
/
/movies
/movies/today
/movies/:iso-date where :iso-date matches ####-##-##
The constraint for the last route is defined with the following snippet:
^:constraints {:iso-date #"\d{4}-\d{2}-\d{2}"}
Whenever the route containing this constraint is present in the route table I am unable to GET its sibling route /movies/today; instead I am getting a "Not Found" response instead. When the constraint-having route is removed, however, a GET of /movies/today succeeds.
The Pedestal routes I have defined using terse format look like so:
(defroutes routes
[[["/" {:get root-page}
["/movies" ^:interceptors [fetch-movies]
{:get movies-page}
["/today" {:get movies-for-today-page}]
["/:iso-date" ^:constraints {:iso-date #"\d{4}-\d{2}-\d{2}"}
{:get movies-for-date-page}]]]]])
Have I constructed this route table correctly in order to achieve the routing behaviour that I want?
NB: Printing the compiled routes gives me the result that I would expect in that all of the routes are present the generated :path-re regexes match as expected at the REPL:
({:path-parts [""],
:path-params [],
:interceptors
[{:name :foobar.service/root-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x14501070 "io.pedestal.interceptor.helpers$before$fn__7359#14501070"],
:leave nil,
:error nil}],
:path "/",
:method :get,
:path-re #"/\Q\E",
:route-name :foobar.service/root-page}
{:path-parts ["" "movies"],
:path-params [],
:interceptors
[{:name :foobar.service/fetch-movies,
:enter
#object[io.pedestal.interceptor.helpers$on_request$fn__7401 0x2aa85cc4 "io.pedestal.interceptor.helpers$on_request$fn__7401#2aa85cc4"],
:leave nil,
:error nil}
{:name :foobar.service/movies-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x30ffc3c0 "io.pedestal.interceptor.helpers$before$fn__7359#30ffc3c0"],
:leave nil,
:error nil}],
:path "/movies",
:method :get,
:path-re #"/\Qmovies\E",
:route-name :foobar.service/movies-page}
{:path-parts ["" "movies" "today"],
:path-params [],
:interceptors
[{:name :foobar.service/fetch-movies,
:enter
#object[io.pedestal.interceptor.helpers$on_request$fn__7401 0x2aa85cc4 "io.pedestal.interceptor.helpers$on_request$fn__7401#2aa85cc4"],
:leave nil,
:error nil}
{:name :foobar.service/movies-for-today-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x3726fc3b "io.pedestal.interceptor.helpers$before$fn__7359#3726fc3b"],
:leave nil,
:error nil}],
:path "/movies/today",
:method :get,
:path-re #"/\Qmovies\E/\Qtoday\E",
:route-name :foobar.service/movies-for-today-page}
{:path-parts ["" "movies" :iso-date],
:path-params [:iso-date],
:interceptors
[{:name :foobar.service/fetch-movies,
:enter
#object[io.pedestal.interceptor.helpers$on_request$fn__7401 0x2aa85cc4 "io.pedestal.interceptor.helpers$on_request$fn__7401#2aa85cc4"],
:leave nil,
:error nil}
{:name :foobar.service/movies-for-date-page,
:enter
#object[io.pedestal.interceptor.helpers$before$fn__7359 0x93fb20b "io.pedestal.interceptor.helpers$before$fn__7359#93fb20b"],
:leave nil,
:error nil}],
:path "/movies/:iso-date",
:path-constraints {:iso-date "(\\d{4}-\\d{2}-\\d{2})"},
:query-constraints {},
:method :get,
:path-re #"/\Qmovies\E/(\d{4}-\d{2}-\d{2})",
:route-name :foobar.service/movies-for-date-page})
I solved this problem in pedestal version 0.4.1-SNAPSHOT.
(io.pedestal.http.route/router my-routes :linear-search)
Use :linear-search, instead of :prefix-tree.

Compojure Routes losing params info

My code:
(defn json-response [data & [status]]
{:status (or status 200)
:headers {"Content-Type" "application/json"}
:body (json/generate-string data)})
(defroutes checkin-app-handler
(GET "/:code" [code & more] (json-response {"code" code "params" more})))
When I load the file to the repl and run this command, the params seems to be blank:
$ (checkin-app-handler {:server-port 8080 :server-name "127.0.0.1" :remote-addr "127.0.0.1" :uri "/123" :query-string "foo=1&bar=2" :scheme :http :headers {} :request-method :get})
> {:status 200, :headers {"Content-Type" "application/json"}, :body "{\"code\":\"123\",\"params\":{}}"}
What am I doing wrong? I need to get at the query-string, but the params map is always empty..
In order to get the query string parsed into the params map, you need to use the params middleware:
(ns n
(:require [ring.middleware.params :as rmp]))
(defroutes checkin-app-routes
(GET "" [] ...))
(def checkin-app-handler
(-> #'checkin-app-routes
rmp/wrap-params
; .. other middlewares
))
Note, that the usage of the var (#'checkin-app-routes) is not strictly necessary, but it makes the routes closure, wrapped inside the middlewares, pick up the changes, when you redefine the routes.
IOW you can also write
(def checkin-app-handler
(-> checkin-app-routes
rmp/wrap-params
; .. other middlewares
))
but then, you need to redefine the handler too, when interactively redefining the routes.