Fork me on GitHub
#fulcro
<
2020-07-25
>
zilti16:07:52

So I am getting "Error: Cannot write component" when trying to trigger a mutation from a defreport's row-actions. The mutation's optimistic action runs fine, the server-side mutation is never run at all. What can be the causes for that?

lgessler17:07:55

how do you all handle the <title> element in your apps? i'm thinking of just mutating it in my routing function

dvingo18:07:15

this is the de facto lib for that in js world https://github.com/nfl/react-helmet

lgessler18:07:12

thanks! i still have to get familiar with react package land...

lgessler19:07:57

btw @U051V5LLP since you're here can i ask you a small question? i'm writing ::pc/transform for authorization based on some code you wrote: https://clojurians-log.clojureverse.org/pathom/2020-06-10. In the transform function, if all goes well then I can return (resolve env params) and that works OK, but if the request is unauthorized then I want to return a server error (as a map). If I just return a plain clojure map I get an error: Exception in thread "async-dispatch-7" java.lang.IllegalArgumentException: No implementation of method: :take! of protocol: #'clojure.core.async.impl.protocols/ReadPort found for class: clojure.lang.PersistentArrayMap

lgessler19:07:24

I'm guessing this means a resolver's output is some kind of wrapped value, but i'm not sure what it is, combing through the pathom docs and code. do you happen to know what the right move is?

dvingo19:07:41

hey np - yea the response has to be wrapped, looking for the helper...

dvingo19:07:26

(pa/assoc-response env {:error "msg"})

lgessler20:07:17

hmm, that seems to be specific for your interceptor approach, right? i'm trying something simpler:

(pc/defresolver all-users-resolver [{:keys [neo4j]} _]
  {::pc/output    [{:all-users [:user/id]}]
   ::pc/transform mc/auth-tx
   :auth          :admin}
  ;; ...
  )

;; --------------
(defn auth-tx [{::pc/keys [resolve] :keys [auth] :as outer-env}]
  (assoc
    outer-env
    (if resolve ::pc/resolve ::pc/mutate)
    (fn [env params]
      (if (authorized env auth)
        (resolve env params)
        (server-error (str "Unauthorized mutation: session "
                           (get-in env [:ring/request :session])
                           " does not satisfy authorization requirement "
                           auth))))))

lgessler20:07:03

it seems like it actually doesn't need to be a wrapped value--puzzlingly, if i return a literal map (`{:all-users []}`) instead of (resolve env params) the response is OK

lgessler20:07:20

but there are other places in my code where I know I return something that doesn't conform to my ::pc/output declaration...

lgessler20:07:28

I'll just as a question in the pathom channel, thank you for the help 🙂

dvingo15:07:21

hey @U49U72C4V sorry I skimmed over your first post and missed that you were asking about a general pc/transform question. This is an example for a plain transform for mutate or resolve

(defn simple-tform
  [{::pc/keys [mutate resolve] :as env}]
  (if resolve
    (assoc env ::pc/resolve
               (fn [en params]
                 (log/info "In tx resolve")
                 (resolve en params)))
    (assoc env ::pc/mutate
               (fn [en params]
                 (log/info "In tx mutate")
                 (mutate en params)))))

(pc/defresolver res1 [_ _]
  {::pc/output    [::test]
   ::pc/transform simple-tform}
  {::test "hello this is my name"})

(pc/defmutation a-mutation
  [{:keys [current-user] :as env} {:keys [] :as props}]
  {::pc/transform simple-tform}
  (log/info "In mutation"))

dvingo15:07:49

invoking resolve will return whatever the body of the resolver returns, so yea you could forego that and return anything you want - like the error map

dvingo15:07:12

looking at your post in pathom - no ideas come to mind other than to try swapping in a different reader instead of the parallel one

👍 3
lgessler16:07:17

that's what wilker suggested over in #pathom too--it mostly does the right thing when I switch to pc/reader instead of pc/parallel-reader . i feel like i'm still doing something wrong since even though it doesn't crash the map that shows up on the client isn't exactly what i returned... but anyway, this is good enough for now 🙂 thanks for the help @U051V5LLP!

dvingo16:07:26

hmm, that is weird, I would check what is returned from the pathom parser on the server and what the client receives. maybe the error keys are not being queried for from the client?

lgessler17:07:39

ok, so i think the issue is that pathom is trimming output it's not expecting from my resolver. i'm using the server-error function which you wrote, which i saw you use in a few mutations e.g. for signup https://github.com/lgessler/glam/blob/master/src/main/glam/models/session.clj#L62

lgessler17:07:21

...but i'm using server-error in a resolver, where the keys from the server-error map are not compatible with the resolver's ::pc/output:

(pc/defresolver all-users-resolver [{:keys [neo4j]} _]
  {::pc/output    [{:all-users [:user/id]}]
   ::pc/transform mc/auth-tx
   :auth          :admin
   }
  {:all-users (->> neo4j
                   user/get-all
                   (map #(take-keys % [:user/id]))
                   vec)})

lgessler17:07:31

i guess it makes sense in this case that the error map shouldn't be returned--that's how resolvers work, they only give back what's written on the tin. i guess maybe it'd be nice if you could error from them like in a mutation but it's not a huge deal i suppose

lgessler22:07:25

leaving my final transform here for posterity. basically, you need an authorized function that will take the env and any other parameters and return true iff the env meets the authorization level you need, and then you use make-auth-transform to make a transform for every authorization level you want to support. then you just include the transform on your mutation/resolver and the mutation/resolver will only work if the authorization required is present

(defn authorized
  "Given a resolver's environment, say whether it is authorized for a given level"
  [env level]
  (let [{:keys [session/valid? user/admin?]} (get-in env [:ring/request :session])]
    (or (nil? level)
        (and (= level :admin) admin?)
        (and (= level :user) valid?))))

;; pathom security transforms
(defn make-auth-transform [level]
  "Make a transform for a Pathom resolver that checks whether the user has sufficient
  permissions for a given operation. mutate? is a bool that indicates whether this is
  for a resolver or a mutation, and level is one of :admin or :user indicating required
  permissions."
  (fn auth-transform [{::pc/keys [mutate resolve] :as outer-env}]
    (let [pathom-action (if mutate mutate resolve)
          pathom-action-kwd (if mutate ::pc/mutate ::pc/resolve)]
      (assoc
        outer-env
        pathom-action-kwd
        (fn [env params]
          (log/info (str "authorized? " (authorized env level)))
          (let [res (if (authorized env level)
                      (pathom-action env params)
                      (server-error (str "Unauthorized pathom action: session "
                                         (get-in env [:ring/request :session])
                                         " does not satisfy authorization requirement "
                                         level)))]
            (log/info (str "auth-tx output: " (pr-str res)))
            res))))))

(def admin-required (make-auth-transform :admin))
(def user-required (make-auth-transform :user))

lgessler22:07:21

and in case you need additional transforms, you should be able to just compose them together

dvingo01:07:52

ah, that makes sense regarding the keys not being in output. For my resolvers I just add the error map keys in output vector. I suppose you could alter the output in a pc/transform to include them as another option

dvingo01:07:43

thanks for sharing your auth code, maybe you can add a note here: https://github.com/souenzzo/eql-style-guide/issues/4

lgessler02:07:59

ooh yeah, modifying the output vector for them's a great idea! 😃

lgessler02:07:57

i'll make a note on that thread soon

alex-eberts22:07:10

I’m having trouble getting the app-db to update in the fulcro-template using the simplest example that I can think of. I’ve tried refreshing the app and still no luck… any ideas? Thanks! https://github.com/aeberts/fulcro-template/blob/e2d8d01b0a0c6a56b5ecbfec90d6ccf9f13ad51c/src/main/app/ui/root.cljs#L139

lgessler02:07:10

I think your issue is that Foo isn't rendering, right? you need to pass props to ui-foo , and in order to do that you need to compose Foo's query and initial state with a new attribute (call it :root/foo, maybe) in Root

lgessler02:07:50

you want sth like this:

(defsc Main [this {:main/keys [foo] :as props}]
  {:query         [:main/welcome-message {:main/foo (comp/get-query Foo)}]
   :initial-state {:main/welcome-message "Hi!"
                   :main/foo             (c/get-initial-state Foo}
   :ident         (fn [] [:component/id :main])
   :route-segment ["main"]}
  (div :.ui.container.segment
    (h3 "Main")
       (ui-foo foo)))

alex-eberts16:07:51

@U49U72C4V Many thanks for the explanation - that did the trick! In case someone else sees this thread here is the completely working version:

(defsc Foo [this {:foo/keys [name] :as props}]
  {:query [:foo/name]
   :initial-state (fn [params] {:foo/name "Alex"})
   :ident (fn [] [:component/id :foo])}
  (div
   (p "Rendered in Foo component: ")
   (p "Hi " name)))

(def ui-foo (comp/factory Foo))

(defsc Main [this {:main/keys [foo welcome-message] :as props}]
  {:query         [:main/welcome-message {:main/foo (comp/get-query Foo)}]
   :initial-state (fn [params]
                    {:main/welcome-message "Hi!"
                     :main/foo (comp/get-initial-state Foo)})
   :ident         (fn [] [:component/id :main])
   :route-segment ["main"]}
  (div :.ui.container.segment
    (h3 "Main Component")
       (p "Main Component Welcome Message: " welcome-message)
       (ui-foo foo)))

fulcro 3