fullstack clojurescript development with Calva Jack-in - express

Please comment how to start backend and frontend with Calva Jack-in.
There is a clojurescript project for a web-application, I started REPL with calva jack-in (deps.edn + shadow-cljs), and tried to evaluate the function start!/stop! in the REPL. Even though there was no error message, I didn't find a corresponding response with "localhost:3000/", it was expected that "Hello, world" should be shown on browser.
To compile of both frontend and backend successfully:
$ npm install && npx shadow-cljs watch frontend backend
Start the server server with node.js successfully
$ node target/main.js
Clojurescript backend code for reference.
(ns mern.backend.core
(:require ["express" :as express]))
;; currently broken in shadow-cljs
(set! *warn-on-infer* true)
(defonce server (atom nil))
(def port 3000)
(defn start-server []
(println "Starting server")
(let [app (express)]
(.get app "/" (fn [_req res] (.send res "Hello, world")))
(.listen app port (fn [] (println "Example app listening on port 3000!")))))
(defn start! []
;; called by main and after reloading code
(reset! server (start-server)))
(defn stop! []
;; called before reloading code
(.close #server)
(reset! server nil))
(defn main []
;; executed once, on startup, can do one time setup here
(start!))

Related

In Clojurescript, how do I use AWS javascript SDK to list S3 buckets?

I am just getting started with Clojurescript. I wrote some clojurescript code to use the shared aws credentials file to initialize S3 client and list buckets . However my code does not work.
(defn -main [arg1 arg2]
(println "hello")
(let[ creds (new AWS/SharedIniFileCredentials #js{:profile "superman"})
_ (AWS/config.update creds)
; dump out the accesskey to check if it has the correct profile
_ (AWS/config.getCredentials (fn [err] (if (nil? err) (do
(println "its good")
(println AWS/config.credentials.accessKeyId)))))
s3 (new (.-S3 AWS ))
] (.listBuckets s3 (fn[err buckets] (println "err: " err) (println buckets) )) ))
The AWS/config.getCredentials in the above does pick up the correct profile as seen from (println AWS/config.credentials.accessKeyId). The listbuckets code throws the following error:
#object[NodeError TypeError [ERR_INVALID_ARG_TYPE]: The "key" argument must be of type string or an instance of Buffer, TypedArray, DataView, or KeyObject. Received undefined]
I have Google AWS SDK S3 clojuresript AND is the only link I found . I used that to configure the S3 client but that does not seem to work
I would appreciate any help.
I checked it and the problem seems to be that SDK expects the credentials to be set before anything else, before instantiating the S3 client.
The following works for me on a minimal project with shadow-cljs:
(ns server.main
(:require ["aws-sdk" :as aws]))
(defn main! []
(println "App loaded...")
(let [creds (aws/SharedIniFileCredentials. #js {:profile "example-profile"})
_ (set! (.-credentials aws/config) creds)
s3 (aws/S3.)]
(.listBuckets s3 (fn [err data]
(if err
(println "ERROR:" err)
(println "OK:" data))))))
when I run it:
$ node target/main.js
App loaded...
OK: #js {:Buckets #js [#js {:Name dummy-test-bucket, :CreationDate #inst "2019-05-05T17:32:17.000-00:00"} #js {:Name mydomain.com, :CreationDate #inst "2018-06-19T04:16:10.000-00:00"}], :Owner #js {:DisplayName username, :ID f63f30bc25ab3609b8d3b5be6b3a872dd2c9f7947b2d509e2338357d93e74f2}}
The key was on this answer: https://stackoverflow.com/a/33175424/483566

Clojure :pre report failing value when destructuring

Following this SO post, I would like to print the value of the preconditions in my function. However it fails for me in the following case (probably destructuring) :
I have a dir? helper function (feel free to skip this one) :
(defn dir? [s]
"returns true if the string passed is is an existing directory"
(->> (clojure.java.io/file s)
((juxt #(.exists %) #(.isDirectory %)))
(every? true?)))
It works just fine, and using the is macro, I get some nice error messages where I can see both the test and the parameters that were passed :
(is (dir? (io/file "resources/static"))) ;; => true
(is (dir? (io/file "resources/statice"))) ;; typo, error below
FAIL in clojure.lang.PersistentList$EmptyList#1
(boot.user4515592986834245937.clj:86) expected: (dir? (io/file
"resources/statice")) actual: (not (dir? #object[java.io.File
0x6730a420 "resources/statice"]))
However, when trying to use it in the precondition :pre, I get an ugly error :
(defn make-something
[&{:keys [dir]
:or {dir "default-dir"}}]
{:pre [(is (dir? (clojure.java.io/file dir)))]}
;;... do something with these
)
(make-something :dir "resources/statices") ;; doesn't exist
clojure.lang.Compiler$CompilerException: java.lang.AssertionError:
Assert failed: (is (dir? (io/file dir))),
compiling:(boot.user4515592986834245937.clj:80:12)
java.lang.AssertionError: Assert failed: (is (dir? (io/file dir)))
How can I get a nice error message in my function just like the one above ?
In case it matters, I am using Clojure 1.7.
You need to check your code (dir? function). Following snippet works for me:
(require '[clojure.java.io :as io])
(defn dir? [s]
(let [dir (io/file s)]
(and (.exists dir)
(.isDirectory dir))))
(defn make-something
[& {:keys [dir] :or {dir "default-dir"}}]
{:pre [(is (dir? dir))]}
(println dir))
(make-something :dir "/tmp")
out => /tmp
ret => nil
(make-something :dir "/xxxx")
FAIL in clojure.lang.PersistentList$EmptyList#1 (form-init3332271312167175068.clj:1)
expected: (dir? dir)
actual: (not (dir? "/xxxx"))
AssertionError Assert failed: (is (dir? dir)) user/make-sth (form-init3332271312167175068.clj:1)

How to add Friend auth to Chestnut template

I've struggled for a few days trying to get a simple use of the security library, Friend, to work with the Chestnut clj/cljs template.
A POST request to the the /login uri is supposed log me in and allow access the protected routes like /role-user. But for some reason I am not able to login, the POST returns a 303 and routes me back to the root page.
I added the Friend middleware inside the http-handler function. Is this the correct place to apply this sort of middleware? I thought maybe the reload or api-defaults middleware could be messing up friend middleware? However, removing them does not fix things.
(def http-handler
(if is-dev?
(-> #'routes
(reload/wrap-reload)
(friend/authenticate
{:allow-anon? true
:login-uri "/login"
:default-landing-uri "/"
:unauthorized-handler #(-> (h/html5 [:h2 "You do not have sufficient privileges to access " (:uri %)])
resp/response
(resp/status 401))
:credential-fn (fn [x]
(let [res (creds/bcrypt-credential-fn #users x)]
(log/info x)
(log/info res)
res))
:workflows [(workflows/interactive-form)]})
(wrap-defaults api-defaults))
(wrap-defaults routes api-defaults)))
Based on the print statements I was able to figure out that the credential-fn function does get called on the POST request, with the correct params, and the function returns the correct (authenticated) result.
This http-handler is used as such
(defn run-web-server [& [port]]
(let [port (Integer. (or port (env :port) 10555))]
(print "Starting web server on port" port ".\n")
(run-jetty http-handler {:port port :join? false})))
(defn run-auto-reload [& [port]]
(auto-reload *ns*)
(start-figwheel))
(defn run [& [port]]
(when is-dev?
(run-auto-reload))
(run-web-server port))
For what it's worth, here are my routes.
(defroutes routes
(GET "/" req
(h/html5
misc/pretty-head
(misc/pretty-body
(misc/github-link req)
[:h3 "Current Status " [:small "(this will change when you log in/out)"]]
[:p (if-let [identity (friend/identity req)]
(apply str "Logged in, with these roles: "
(-> identity friend/current-authentication :roles))
"anonymous user")]
login-form
[:h3 "Authorization demos"]
[:ul
[:li (e/link-to (misc/context-uri req "role-user") "Requires the `user` role")]]
[:p (e/link-to (misc/context-uri req "logout") "Click here to log out") "."])))
(GET "/login" req
(h/html5 misc/pretty-head (misc/pretty-body login-form)))
(GET "/logout" req
(friend/logout* (resp/redirect (str (:context req) "/"))))
(GET "/role-user" req
(friend/authorize #{::users/user} "You're a user!")))
I figured it out. (wrap api-defaults) does not allow sessions and Friend is trying to use them. I should be using site-defaults instead. See ring middleware for more info.

clojure.test unable to resolve symbol from `eval`. Works in REPL and lein run

Please bear with this contrived example but it was the simplest thing I could think of to recreate the issue.
(ns something.core)
(defn call-foo [something & args]
(let [a-foo (:foo (eval (:quux something)))]
(apply a-foo args)))
(def Something {
:foo (fn [& args] args)
:bar (fn [something] (call-foo something))
})
(defn make-something []
{:quux 'Something})
Running the following in the REPL or with lein run works well.
(let [subject (make-something)
actual (call-foo subject "hello" "greetings")]
(println actual))
;;=> (hello greetings)
The problem occurs only during this test and executing lein test:
(ns something.core-test
(:require [clojure.test :refer :all]
[something.core :refer :all]))
(deftest a-test
(let [subject (make-something)
actual (call-foo subject "hello" "greetings")]
(is (= ["hello" "greetings"] actual))))
This throws an error. An example output:
ERROR in (a-test) (Compiler.java:6464)
Uncaught exception, not in assertion.
expected: nil
actual: clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to resolve symbol: Something in this context, compiling:(/private/var/folders/0n/c7q7860j34xfc2r1x4q51jrh0000gn/T/form-init9215140948330409114.clj:1:6436)
The line "Unable to resolve symbol: Something in this context" makes me think Something is not in context for some reason while I eval in call-foo. But why is this the case only in the test?
The problem is that eval does not see context. Your 'Something resolves in something.core and something.core-test since you have refered all. It won't resolve from whatever namespace where lein test runs its tests.
To fix the immediate problem change
'Something
to
`Something
so that it is namespace-qualified. The test will then run (and fail), but that's another issue (println returns nil for one thing).

How to serve the stream pdf with ring

I'm trying to serve a clj-http generated document directly via ring/compojure.
I thought ring.util/piped-output-stream would work, but it seems I'm not understanding something here...
This:
(defn laminat-pdf-t
[natno]
(piped-input-stream
(fn [output-stream])
(pdf
[ {:title (str "Omanimali-Kuscheltierpass" natno)
:orientation :landscape
:size :a6
:author "Omanimali - Stefanie Tuschen"
:register-system-fonts true
}
;; [:svg {} (clojure.java.io/file
;; (str "/einbuergern/" natno "/svg" ))]
[:paragraph "Some Text"] ]
output-stream)))
(defn laminat-pdf
"generate individualized cuddly toy passport page"
[natno]
{:headers {"Content-Type" "application/pdf"}
:body (laminat-pdf-t natno)})
leads to an empty response...
What do I need to do differently?
Thanks,
Mathias
I think you may have a bracket out of place in your code (look at the laminat-pdf-t function below - I tweaked it slightly).
Here's exactly what I did (first creating a project with leiningen 2.3.4 called pdf-play) and it displayed a PDF correctly in IE 11.0.9600.16521, Firefox 28.0 and Chrome 33.0.1750.154 (all on Windows - sorry these are the only browsers that I have installed and I don't have a Linux or Mac box but I don't think the browser makes any difference):
project.clj
(defproject pdf-play "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.5.1"]
[compojure "1.1.6"]
[clj-pdf "1.11.15"]]
:plugins [[lein-ring "0.8.10"]]
:ring {:handler pdf-play.handler/app})
src/pdf_play/handler.clj
(ns pdf-play.handler
(:use compojure.core
ring.util.io
clj-pdf.core)
(:require [compojure.handler :as handler]
[compojure.route :as route]))
(defn laminat-pdf-t
[natno]
(piped-input-stream
(fn [output-stream]
(pdf
[{:title (str "Omanimali-Kuscheltierpass" natno)
:orientation :landscape
:size :a6
:author "Omanimali - Stefanie Tuschen"
:register-system-fonts true
}
;; [:svg {} (clojure.java.io/file
;; (str "/einbuergern/" natno "/svg" ))]
[:paragraph "Some Text"]]
output-stream))))
(defn laminat-pdf
"generate individualized cuddly toy passport page"
[natno]
{:headers {"Content-Type" "application/pdf"}
:body (laminat-pdf-t natno)})
(defroutes app-routes
(GET "/" [] (laminat-pdf 1234))
(route/resources "/")
(route/not-found "Not Found"))
(def app (handler/site app-routes))
Then started it at the command prompt like so:
lein ring server
and had a look in the browser and there was a PDF with "Some Text" printed in it.