How do I remove redundancy in this clojure code? - clojure

I have written the following code:
(defprotocol MusicFile
(get-artist [this])
(get-song [this])
(get-album [this]))
(defrecord Mp3 [filename]
MusicFile
(get-artist [this]
(let [info (get-all-info (:filename this))]
(:artist info)))
(get-song [this]
(let [info (get-all-info (:filename this))]
(:title info)))
(get-album [this]
(let [info (get-all-info (:filename this))]
(:album info))))
Is there a easyway to remove redundancy in this code?

The fields of the record are in scope within methods defined inside the record definition.
(defrecord Mp3 [filename]
MusicFile
(get-artist [this]
(:artist (get-all-info filename)))
(get-song [this]
(:title (get-all-info filename)))
(get-album [this]
(:album (get-all-info filename))))

Related

Google Indexing Api in clojure

(:require [utils.base64 :as base64])
(:import [com.google.api.client.googleapis.auth.oauth2 GoogleCredential]
[com.google.api.services.indexing.v3 Indexing]
[com.google.api.services.indexing.v3.model UrlNotification]
[com.google.api.client.http HttpTransport]
[com.google.api.client.json.gson GsonFactory]
[java.io ByteArrayInputStream]
[java.util Base64]
[com.google.api.services.indexing.v3 IndexingScopes]))
;; Set the private key and client email
(def private-key "")
(def client-email "my-client-email")
(def encoded-private-key (base64/encode-string private-key))
;; Set the private key and client email
(def private-key-json '{
"type": "service_account",
"client_email": "' client-email '",
"private_key": "' encoded-private-key '"
}')
;; Create the input stream from the JSON string
(def input-stream (ByteArrayInputStream. (.getBytes private-key-json)))
;; Create the OAuth 2.0 credentials
(def credentials (doto (GoogleCredential/fromStream input-stream)
(.createScoped (java.util.Collections/singleton IndexingScopes/INDEXING))))
;; Set up the HTTP transport and JSON factory
(def http-transport (HttpTransport/newTrustedTransport))
(def json-factory (GsonFactory/getDefaultInstance))
;; Set up the Indexing API client
(def indexing-client (doto (Indexing/Builder. http-transport json-factory credentials)
(.setApplicationName "My Indexing App")
(.build)))
;; Publish a URL notification
(def url-notification (UrlNotification. "https://aaa.com" "URL_UPDATED"))
(.execute (indexing-client/urlNotifications) (publish url-notification))
I'm trying to use private-key-json but the format is not valid. What is the best way to pass data? I referred https://developers.google.com/search/apis/indexing-api/v3/prereqs#examples.
Here I'm trying not to upload json file which we get once we create a service account and make use of only mandatory fields like client_email, private_key and type fields.
Modified code:
(:require [cheshire.core :as json]
[sketches.utils.base64 :as base64]
[clojure.string :as str])
(:import [com.google.api.client.googleapis.auth.oauth2 GoogleCredential]
[com.google.api.services.indexing.v3 Indexing$Builder]
[com.google.api.services.indexing.v3.model UrlNotification]
[com.google.api.client.googleapis.javanet GoogleNetHttpTransport]
[com.google.api.client.http HttpTransport]
[com.google.api.client.json.gson GsonFactory]
[java.io ByteArrayInputStream]
[com.google.api.services.indexing.v3 IndexingScopes]))
(def creds-json {:type "service_account",
:client_email "test-indexing-api",
:client_id "117578194507835125202",
:private_key_id "964321e5fc29980944da518887116ea50dfb7803",
:private_key ""})
(defn string->stream
([s] (string->stream s "UTF-8"))
([s encoding]
(-> s
(.getBytes encoding)
(ByteArrayInputStream.))))
(defn authorize [cred]
(-> cred
(json/encode)
(string->stream)
(GoogleCredential/fromStream)
(.createScoped [(IndexingScopes/INDEXING)])))
(defn valid-private-key [private-key]
(try
(when private-key
(->> (str/trim private-key)
(re-matches #"^-----BEGIN PRIVATE KEY-----[\s|\S]*-----END PRIVATE KEY-----[\\|n]*")
(some?)))
(catch Exception e
false)))
(def encoded-private-key (base64/encode-string private-key))
(def private-creds-json-string (json/generate-string creds-json))
(def input-stream (ByteArrayInputStream. (.getBytes private-creds-json-string)))
(defn build-analytics-client [creds]
(-> (Indexing$Builder. (GoogleNetHttpTransport/newTrustedTransport) (GsonFactory/getDefaultInstance) (authorize creds))
(.build)))
(defn url-notification []
(-> (UrlNotification.)
(.setType "URL_update")
(.setUrl "http://ace.madrid.quintype.io")))
(defn realtime-data [creds]
(-> (build-analytics-client creds)
(.urlNotifications)
(.publish (url-notification))
(.execute)))
```
But I get this error `Execution error (NoSuchMethodError) at com.google.api.client.http.ConsumingInputStream/close (ConsumingInputStream.java:40).
com.google.common.io.ByteStreams.exhaust(Ljava/io/InputStream;)J`

how to fix issue with body-params values not being picked up by clojure muuntaja middleware

I'm trying to adapt the code here https://github.com/danownsthisspace/shorturl/blob/main/src/shorturl/core.clj with this:
(ns todo.core
(:require [clojure.pprint :as pprint]
[muuntaja.core :as m]
[reitit.ring :as ring]
[reitit.ring.middleware.muuntaja :as muuntaja]
[ring.adapter.jetty :as ring-jetty]
[ring.util.response :as r]
[todo.db :as db]))
(defn todo-items-save [req]
(clojure.pprint/pprint req)
(let [title (get-in req [:body-params :title])
content (get-in req [:body-params :content])]
(r/response (str "foooo" title))))
(def app
(ring/ring-handler
(ring/router
[["/"
["" {:handler (fn [req] {:body "hello" :status 200})}]]
["/api/todo" {:post {:handler todo-items-save}
:get (fn [req]
(let [todos db/get-todos]
(r/response todos)))}]
{:data {:muuntaja m/instance :middleware [muuntaja/format-middleware]}}])))
(defn start []
(ring-jetty/run-jetty #'app {:port 3002 :join? false}))
(def server (start))
(.stop server)
but I'm seeing that the body-params values are null. I was wondering why that is when I'm making a post request with the body {"content":"only a test", "title":"second"}. Thank you.
The problem was, that the muuntaja config was passed as part of the
routes instead of argument to ring/router
(def app
(ring/ring-handler
(ring/router
[["/"
["" {:handler (fn [req] {:body "hello" :status 200})}]]
["/api/todo" {:post {:handler todo-items-save}
:get (fn [req]
(let [todos db/get-todos]
(r/response todos)))}]]
; XXX this must be the second argument to `ring/router`
{:data {:muuntaja m/instance
:middleware [muuntaja/format-middleware]}})))

How to save the file in the server?

I'm receiving the following request in the server that I'm extracting using (-> req :params):
{"_parts":[["video",{"_data":{"size":2971246,"blobId":"D002459C-47C5-4403-ABC6-A2DE6A46230A","offset":0,"type":"video/quicktime","name":"DCDE604A-954F-4B49-A1F9-1BCC2C2F37BC.mov","__collector":null}}],["key","VAL"]]}
It contains a file "video" with a name and blobId. However, I want to access the file's data and save it to a file. So far, I've tried the following:
(defn upload-shot-video [req]
(prn "uploading video")
(prn "video is! " (-> req :multipart-params))
(prn "video is " (-> req :params))
(prn "video before is " (slurp (-> req :body)))
(.reset (-> req :body))
(prn "req full" (-> req))
(prn "video after is " (-> req :body))
(prn "video is! " (-> req :multipart-params))
(clojure.java.io/copy (-> req :body) (clojure.java.io/file "./resources/public/video.mov"))
(let [filename (str (rand-str 100) ".mov")]
(s3/put-object
:bucket-name "humboi-videos"
:key filename
:file "./resources/public/video.mov"
:access-control-list {:grant-permission ["AllUsers" "Read"]})
(db/add-video {:name (-> req :params :name)
:uri (str "https://humboi-videos.s3-us-west-1.amazonaws.com/" filename)}))
(r/response {:res "okay!"}))
In which I'm trying to save the (-> req :body) into the file (which is an inputstream). This must be incorrect. What's the correct way to save this file that the server has received into a file, by saving the data into a file on the server? How to extract this data from the request?
If you are using Ring, you need to use wrap-multipart-params middleware.
(ns controller
(:require [ring.middleware.params :refer [wrap-params]]
[ring.middleware.multipart-params :refer [wrap-multipart-params]])
(defn upload-shot-video [req]
(let [uploaded-file (-> req :params "file" :tempfile) ;; here is a java.io.File instance of your file
(save-file uploaded-file)
{:status 201 :body "Upload complete"}))
(def app
(-> upload-shot-video
wrap-params
wrap-multipart-params))

Pedestal early termination is not working

http://pedestal.io/reference/servlet-interceptor Says this
Before invoking the :enter functions, the servlet interceptor sets up a "terminator" predicate on the context. It terminates the interceptor chain when the context map returned by an interceptor has a response map attached.
My server has this:
(ns wp-server.server
(:gen-class) ; for -main method in uberjar
(:require
[io.pedestal.http :as server]
[io.pedestal.http.route :as route]
[wp-server.service :as service]
[wp-server.datomic :as datomic]))
(defonce runnable-service (atom nil))
(defn -main
"The entry-point for 'lein run'"
[& args]
(println "/nConnecting to datomic...")
(datomic/connect!)
(println "\nCreating your server...")
(reset! runnable-service (server/create-servlet service/service))
(server/start runnable-service))
(defn create-runnable-dev-service
"The entry-point for 'lein run-dev'"
[& args]
(println "\nConnecting to [DEV] database...")
(datomic/connect-dev!)
(println "\nCreating your [DEV] server...")
(-> service/service
(merge {:env :dev
::server/join? false
::server/routes #(route/expand-routes (deref #'service/routes))
::server/allowed-origins {:creds true :allowed-origins (constantly true)}
::server/secure-headers {:content-security-policy-settings {:object-src "none"}}})
server/default-interceptors
server/dev-interceptors
server/create-servlet))
(defn start-dev []
(when-not #runnable-service
(reset! runnable-service (create-runnable-dev-service)))
(server/start #runnable-service))
(defn stop-dev []
(server/stop #runnable-service))
(defn restart-dev []
(stop-dev)
(start-dev))
My service looks like this:
(ns wp-server.service
(:require
[datomic.api :as d]
[io.pedestal.http :as http]
[io.pedestal.http.body-params :as body-params]
[io.pedestal.http.route :as route]
[ring.util.response :as ring-resp]
[wp-server.datomic :as datomic]
[wp-common.client :as wp-common-client]
[wp-common.core :as wp-common-core]
[clojure.spec.alpha :as s]
[ring.util.response :as ring-response]))
(defn about-page
[request]
(ring-resp/response (format "Clojure %s - served from %s"
(clojure-version)
(route/url-for ::about-page))))
(def home-page
{:name :home-page
:enter (fn [context]
(prn "TWO")
(assoc context :response {:status 200 :body "Hello, world!"}))})
(defn db-test-page
[{:keys [:database]}]
(ring-resp/response
(prn-str
(d/q '[:find ?text
:where
[?e :advisor/first-name ?text]]
database))))
(def common-interceptors [datomic/db-interceptor (body-params/body-params) http/html-body])
(defn create-spec-validator
[spec]
{:name :validate-spec
:enter
(fn [{{:keys [:edn-params]} :request :as context}]
(prn "ONE")
(if-let [explained (s/explain-data spec edn-params)]
(assoc context
:response {:status 400 :body explained})))})
(def routes #{
;["/" :get (conj common-interceptors `home-page)]
["/clients" :post (conj common-interceptors (create-spec-validator ::wp-common-client/schema) home-page)]
["/db-test" :get (conj common-interceptors `db-test-page)]
["/about" :get (conj common-interceptors `about-page)]})
(def service {:env :prod
::http/routes routes
::http/type :jetty
::http/port 5000
::http/container-options {:h2c? true
:h2? false
:ssl? false}})
When sending a request to localhost:5000/clients with a body that does not pass spec, the create-spec-validator interceptor adds a response to the context. I have confirmed this by logging the context in the home-page interceptor. I would expect the home-page interceptor to be skipped as per documentation. This does not happen. Instead the :enter function of the home-page interceptor is called and the response overwritten.
Why isn't the home-page interceptor being skipped when the create-spec-validator prior to it is returning context with a response?
If you track down the termination code, it invokes
(defn response?
"True if the supplied value is a valid response map."
{:added "1.1"}
[resp]
(and (map? resp)
(integer? (:status resp))
(map? (:headers resp))))
to test if there is a valid response-map in :response. Try setting an empty map in :headers in your response: it should terminate then.
Ideally, of course, you'll set Content-Type to be something meaningful.

How to use http.kit server with drawbridge ring handler?

I have following server code:
(ns tweet-sentiment.server
(:require [clojure.java.io :as io]
[tweet-sentiment.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel start-less]]
[compojure.core :refer [GET POST defroutes]]
[compojure.route :refer [resources]]
[net.cgrand.enlive-html :refer [deftemplate]]
[net.cgrand.reload :refer [auto-reload]]
[ring.middleware.reload :as reload]
[ring.middleware.defaults :refer [wrap-defaults api-defaults]]
[tweet-sentiment.utils :refer [generate-response]]
[environ.core :refer [env]]
[org.httpkit.server :refer [run-server]]
[ring.middleware.edn :refer [wrap-edn-params]]
[tweet-sentiment.tweets :refer [tweets]]
[tweet-sentiment.dandelion :refer [dandelion-sentiment]]
[clojure.core.async :refer [>!! <!! put! take! pipe chan]]
[cemerick.drawbridge :as drawbridge]
[ring.middleware.basic-authentication :refer [wrap-basic-authentication]]
)
(:gen-class))
(defonce server (atom nil))
(deftemplate page (io/resource "index.html") []
[:body] (if is-dev? inject-devmode-html identity))
(defroutes routes
(resources "/")
(resources "/react" {:root "react"})
(GET "/*" req (page)))
(defn wrap-drawbridge [handler]
(fn [req]
(if (= "/repl" (:uri req))
(drawbridge/ring-handler req)
(handler req))))
(def http-handler
(if is-dev?
(-> #'routes
(wrap-defaults api-defaults)
reload/wrap-reload
wrap-edn-params
wrap-drawbridge)
(-> (wrap-defaults #'routes api-defaults)
wrap-edn-params
wrap-drawbridge)))
(defn run-web-server [& [port]]
(let [port (Integer. (or port (env :port) 10555))]
(println (format "Starting web server on port %d." port))
(reset! server (run-server http-handler {:port port :join? false}))))
(defn run-auto-reload [& [port]]
(auto-reload *ns*)
(start-figwheel)
(start-less))
(defn run [& [port]]
(when is-dev?
(run-auto-reload))
(run-web-server port))
(defn stop-server []
(when-not (nil? #server)
(#server :timeout 0)
(reset! server nil)))
(defn restart-server []
(stop-server)
(run-web-server))
(defn -main [& [port]]
(run port))
When I try to connect to drawbridge REPL as dev or production:
lein repl :connect http://localhost:10555/repl
I get following error:
ERROR - POST /repl
java.lang.IllegalArgumentException: No value supplied for key: {:remote-addr "127.0.0.1", :headers {"accept-encoding" "gzip, deflate", "connection" "close", "content-length" "48", "content-type" "application/x-www-form-urlencoded", "host" "localhost:10555", "user-agent" "Apache-HttpClient/4.3.3 (java 1.5)"}, :async-channel #<AsyncChannel /127.0.0.1:10555<->/127.0.0.1:60374>, :server-port 10555, :content-length 48, :websocket? false, :content-type "application/x-www-form-urlencoded", :character-encoding "utf8", :uri "/repl", :server-name "localhost", :query-string nil, :body #<BytesInputStream BytesInputStream[len=48]>, :scheme :http, :request-method :post}
at clojure.lang.PersistentHashMap.create(PersistentHashMap.java:77)
at cemerick.drawbridge$ring_handler.doInvoke(drawbridge.clj:49)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at tweet_sentiment.server$wrap_drawbridge$fn__21884.invoke(server.clj:36)
at org.httpkit.server.HttpHandler.run(RingHandler.java:91)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Problem is obviously in drawbridge/ring-handler function.
These are my dependencies:
[[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-3058" :scope "provided"]
[ring "1.3.2"]
[ring/ring-defaults "0.1.4"]
[compojure "1.3.2"]
[enlive "1.1.6"]
[org.omcljs/om "0.8.8"]
[om-sync "0.1.1"]
[environ "1.0.0"]
[http-kit "2.1.19"]
[fogus/ring-edn "0.3.0"]
[prismatic/om-tools "0.3.11"]
[secretary "1.2.3"]
[sablono "0.3.4"]
[twitter-api "0.7.8"]
[racehub/om-bootstrap "0.5.3"]
[cheshire "5.2.0"]
[com.cemerick/drawbridge "0.0.7"]
[ring-basic-authentication "1.0.1"]]
Did anybody get working http.kit server with drawbrige? What am I missing?
I eventually figured it out, server file should look somewhat like this
(ns tweet-sentiment.server
(:require [clojure.java.io :as io]
[tweet-sentiment.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel start-less]]
[compojure.core :refer [GET POST defroutes]]
[compojure.route :refer [resources]]
[net.cgrand.enlive-html :refer [deftemplate]]
[net.cgrand.reload :refer [auto-reload]]
[ring.middleware.reload :as reload]
[ring.middleware.defaults :refer [wrap-defaults api-defaults]]
[tweet-sentiment.utils :refer [generate-response]]
[environ.core :refer [env]]
[org.httpkit.server :refer [run-server]]
[ring.middleware.edn :refer [wrap-edn-params]]
[clojure.core.async :refer [>!! <!! put! take! pipe chan]]
[cemerick.drawbridge :as drawbridge]
[ring.middleware.basic-authentication :refer [wrap-basic-authentication]]
[ring.middleware.params :as params]
[ring.middleware.keyword-params :as keyword-params]
[ring.middleware.nested-params :as nested-params]
[ring.middleware.session :as session]
)
(:gen-class))
(defonce server (atom nil))
(deftemplate page (io/resource "index.html") []
[:body] (if is-dev? inject-devmode-html identity))
(defroutes routes
(resources "/")
(resources "/react" {:root "react"})
(GET "/*" req (page)))
(defn authenticated? [name pass]
(= [name pass] [(System/getenv "AUTH_USER") (System/getenv "AUTH_PASS")]))
(def drawbridge-handler
(-> (drawbridge/ring-handler)
(keyword-params/wrap-keyword-params)
(nested-params/wrap-nested-params)
(params/wrap-params)
(session/wrap-session)))
(defn http-handler [handler]
(-> handler
(wrap-defaults api-defaults)
wrap-edn-params))
(defn wrap-http [handler]
(fn [req]
(let [handler (if (= "/repl" (:uri req))
(wrap-basic-authentication drawbridge-handler authenticated?)
(if is-dev?
(-> handler
http-handler
reload/wrap-reload)
(-> handler
http-handler)))]
(handler req))))
(defn run-web-server [& [port]]
(let [port (Integer. (or port (env :port) 10555))]
(println (format "Starting web server on port %d." port))
(reset! server
(run-server (wrap-http #'routes) {:port port :join? false})
)))
(defn run-auto-reload [& [port]]
(auto-reload *ns*)
(start-figwheel)
(start-less))
(defn run [& [port]]
(when is-dev?
(run-auto-reload))
(run-web-server port))
(defn stop-server []
(when-not (nil? #server)
(#server :timeout 0)
(reset! server nil)))
(defn restart-server []
(stop-server)
(run-web-server))
(defn -main [& [port]]
(run port))