Fork me on GitHub
#fulcro
<
2021-10-07
>
sheluchin12:10:09

I'm having trouble getting file uploads to work as described in https://book.fulcrologic.com/#FileUpload. My server-side mutation does not seem to receive ::file-upload/files, instead getting:

#p[sheluchin.mutations/import-notes:10] params => #:com.fulcrologic.fulcro.networking.file-upload
{:uploads [#:file {:content {}, :name "test.txt"}]}
I think I have my middleware setup correctly:
;; server
(defn wrap-api [handler uri]
  (fn [request]
    (if (= uri (:uri request))
      (server/handle-api-request
        (:transit-params request)
        (fn [tx] (api-parser {:ring/request request} tx)))
      (handler request))))

(def middleware
  (-> not-found-handler
    (wrap-api "/api")
    (file-upload/wrap-mutation-file-uploads {})
    (server/wrap-transit-params)
    (server/wrap-transit-response)
    (wrap-multipart-params)
    (wrap-resource "public")
    wrap-content-type))


;; client
(def request-middleware
  (->
    (http/wrap-fulcro-request)
    (file-upload/wrap-file-upload)))

(defonce app
  (app/fulcro-app
    {:remotes
     {:remote (http/fulcro-http-remote
                {:url "/api"
                 :request-middlware request-middleware})}}))
Any tips for what I might be doing wrong? I tried to do some debugging and found that this always returns false:
#p[com.fulcrologic.fulcro.networking.file-upload/wrap-mutation-file-uploads:75] (transaction-with-uploads? req) => false

tony.kay14:10:18

All of that looks fine…how are you sending the files?

tony.kay14:10:30

(I use a similar setup in multiple apps)

tony.kay14:10:26

Might be missing some other std middleware…like wrap-keyword-params

tony.kay14:10:00

The docstring on wrap-mutation-file-uploads lists is as a required one

sheluchin14:10:44

I added that one but still no luck:

(def middleware
  (-> not-found-handler
    (wrap-api "/api")
    (file-upload/wrap-mutation-file-uploads {})
    (server/wrap-transit-params)
    (server/wrap-transit-response)
    (wrap-keyword-params)
    (wrap-multipart-params)
    (wrap-resource "public")
    wrap-content-type))
I'm also not sure where wrap-file-uploads comes from, GH doesn't seem to know about it https://github.com/search?q=wrap-file-uploads&amp;type=code I'm sending the file by submitting to this input:
(input                                                                               
  {:type "file"                                                                                              
   :accept ".txt"                                                                    
   :onChange (fn [evt]                                                               
               (let [files (file-upload/evt->uploads evt)                            
                     params {}]                                                      
                 (comp/transact! app                                                 
                  [(m/import-notes (file-upload/attach-uploads params files))])))})) 

sheluchin14:10:40

The parser called in wrap-api is the pathom parser, correct? I have it defined as such:

(defn api-parser
  ([query] (api-parser {} query))
  ([env query]
   (log/info "Process" query)
   (pathom-parser #p env query)))
and it logs:
2021-10-07T14:38:08.946Z xps INFO [sheluchin.parser:43] - Process [(sheluchin.mutations/import-notes {:com.fulcrologic.fulcro.networking.file-upload/uploads [{:file/name "test.txt", :file/content {}}]})]

tony.kay15:10:40

I use wrap-defaults, with the following options:

:ring.middleware/defaults-config
                       {:params    {:keywordize true
                                    :multipart  true
                                    :nested     true
                                    :urlencoded true}
                        :cookies   true
                        :responses {:absolute-redirects     true
                                    :content-types          true
                                    :default-charset        "utf-8"
                                    :not-modified-responses true}
                        :static    {:resources "public"}
                        :session   true
                        :security  {:anti-forgery   false   
                                    :hsts           true
                                    :ssl-redirect   false
                                    :frame-options  :sameorigin
                                    :xss-protection {:enable? true
                                                     :mode    :block}}}
in dev at least

tony.kay15:10:28

but what you have is all that should be required…could be how you’re submitting the files

tony.kay15:10:49

did you look at the low-level network request? It is a multipart form submission with the files attached as parts.

sheluchin15:10:56

Hmm, inspecting the request gives me this error, which I don't see show up in the REPL anywhere:

[
    "^ ",
    "~$sheluchin.mutations/import-notes",
    "class java.lang.IllegalArgumentException: Cannot open <nil> as a Reader."
]

sheluchin17:10:16

> could be how you’re submitting the files Could you elaborate on this point? Do you mean I need to do more in the UI than just put an input with an onChange that submits the transaction? There's nothing wrapping it right now, it's right in the root component. I'm just trying to make it as simple as possible until it works. Short of me being unaware of something obvious, I guess I'm going to go through the ring docs and get a deeper understanding of what all the middleware code is doing. Can't hurt 🙂

tony.kay21:10:39

I think your problem is that you have to properly add the file uploads TO the mutation

tony.kay21:10:17

(comp/transact! this [(settings/save-settings (file-upload/attach-uploads params files))])

tony.kay21:10:32

where files is generated by evt->uploads

tony.kay21:10:48

on an input of type file

tony.kay21:10:43

The js console in chrome, network tab (not fulcro inspect network)…you can click on the network request that contains files, and it should ahve a mime type of multipart/form-data. If you then show the raw content, you’ll see sections separated by a random string boundary. The file(s) will be base64 encoded content in there, and the transaction will be encoded in the top part. If it does not look like that, then the client is the problem

sheluchin12:10:54

That snippet looks exactly like what I have:

(input                                                                                
  {:type "file"                                                                                                     
   :accept ".txt"                                                                     
   ; :multiple true                                                                   
   :onChange (fn [evt]                                                                
               (let [files (file-upload/evt->uploads evt)                             
                     params {}]                                                       
                 (comp/transact! this                                                 
                  [(m/import-notes (file-upload/attach-uploads params #p files))])))})
But it is not coming across as multipart/form-data. Request headers:
POST /api HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 174
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: ";Not A Brand";v="99", "Chromium";v="94"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36
sec-ch-ua-platform: "Linux"
Content-Type: application/transit+json
Accept: */*
Origin: 
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: 
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
app setup:
(ns sheluchin.application
  (:require
    [com.fulcrologic.fulcro.application :as app]
    [com.fulcrologic.fulcro.networking.http-remote :as http]
    [com.fulcrologic.fulcro.networking.file-upload :as file-upload]
    [sheluchin.specs]))

(def request-middleware
  (->
    (http/wrap-fulcro-request)
    (file-upload/wrap-file-upload)))

(defonce app
  (app/fulcro-app
    {:remotes
     {:remote (http/fulcro-http-remote
                {:url "/api"
                 :request-middlware request-middleware})}}))

tony.kay13:10:40

middleware is misspelled on the last line

sheluchin13:10:04

My God... deepest apologies for taking up your time. On the bright side, it gave me a reason to look through a good bit of Fulcro source. Many thanks for your endless patience @U0CKQ19AQ.

tony.kay13:10:51

sure. I’m building up my two requisite tech-support answers…instead of “Is it plugged in” and “did you turn it off/on again”, mine seem to be “Did you check the query/ident/initial-state”? and “Are things spelled right?”

sheluchin13:10:22

Those are going on a sticky note right next to my monitor 🙂

✔️ 1