Files
integreat/src/clj/auto_ap/graphql/plaid.clj

174 lines
8.0 KiB
Clojure

(ns auto-ap.graphql.plaid
(:require
[auto-ap.datomic
:refer [add-sorter-fields apply-pagination apply-sort-3 conn merge-query]]
[auto-ap.graphql.utils
:refer [->graphql
<-graphql
assert-admin
assert-can-see-client
assert-present
attach-tracing-resolvers
limited-clients]]
[auto-ap.plaid.core :as p]
[clj-time.coerce :as coerce]
[clj-time.core :as time]
[clojure.tools.logging :as log]
[datomic.api :as d]))
(defn plaid-link-token [context value _]
(when-not (:client_id value)
(throw (ex-info "Client ID is required" {:validation-error "Client ID is required"})))
(assert-can-see-client (:id context) (:client_id value))
(let [client-code (:client/code (d/pull (d/db conn) [:client/code] (:client_id value)))]
{:token (p/get-link-token client-code)}))
(defn link-plaid [context value _]
(when-not (:client_code value)
(throw (ex-info "Client not provided" {:validation-error "Client not provided."})))
(when-not (:public_token value)
(throw (ex-info "Public token not provided" {:validation-error "public token not provided"})))
(log/info (:id context) (:db/id (d/pull (d/db conn) [:db/id] [:client/code (:client_code value)])))
(assert-can-see-client (:id context) (:db/id (d/pull (d/db conn) [:db/id] [:client/code (:client_code value)])))
(let [access-token (:access_token (p/exchange-public-token (:public_token value) (:client_code value)))
account-result (p/get-accounts access-token )
item {:plaid-item/client [:client/code (:client_code value)]
:plaid-item/external-id (-> account-result :item :item_id )
:plaid-item/access-token access-token
:plaid-item/status (or (some-> account-result :item :error)
"SUCCESS")
:plaid-item/last-updated (coerce/to-date (time/now))
:db/id "plaid-item"}]
@(d/transact conn (->> (:accounts account-result)
(map (fn [a]
(let [balance (some-> a :balances :current (* 0.01))]
(cond-> {:plaid-account/external-id (:account_id a)
:plaid-account/number (:mask a)
:plaid-account/name (str (:name a) " " (:mask a))
:plaid-item/_accounts "plaid-item"}
balance (assoc :plaid-account/balance balance)))))
(into [item])))
(log/info "Access token was " access-token)
{:message (str "Plaid linked successfully.")}))
(def default-read '[:db/id
:plaid-item/external-id
:plaid-item/last-updated
:plaid-item/status
{:plaid-item/accounts [:db/id
:plaid-account/external-id
:plaid-account/number
:plaid-account/balance
:plaid-account/name]}])
(defn raw-graphql-ids [db args]
(let [query (cond-> {:query {:find []
:in ['$]
:where []}
:args [db]}
(:sort args) (add-sorter-fields {"external-id" ['[?e :plaid-item/external-id ?sort-external-id]]}
args)
(limited-clients (:id args))
(merge-query {:query {:in ['[?xx ...]]
:where ['[?e :plaid-item/client ?xx]]}
:args [ (set (map :db/id (limited-clients (:id args))))]})
(:client-id args)
(merge-query {:query {:in '[?client-id]
:where ['[?e :plaid-item/client ?client-id]]}
:args [(:client-id args)]})
true
(merge-query {:query {:find ['?e]
:where ['[?e :plaid-item/external-id]]}}))]
(cond->> query
true (d/query)
true (apply-sort-3 args)
true (apply-pagination args))))
(defn graphql-results [ids db _]
(let [results (->> (d/pull-many db default-read ids)
(group-by :db/id))]
(->> ids
(map results)
(map first))))
(defn get-graphql [args]
(let [db (d/db conn)
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
[(graphql-results ids-to-retrieve db args)
matching-count]))
(defn get-plaid-item-page [context args _]
(let [args (assoc args :id (:id context))
[plaid-items cnt] (get-graphql (<-graphql (assoc args :id (:id context))))]
{:plaid_items (->> plaid-items
(map #(update % :plaid-item/last-updated coerce/from-date))
(map ->graphql))
:total cnt
:count (count plaid-items)
:start (:start args 0)
:end (+ (:start args 0) (count plaid-items))}))
(defn delete-plaid-item [context args _]
(assert-admin (:id context))
(assert-present args :id)
@(d/transact conn [[:db/retractEntity (:id args)]])
{:message "Item deleted."})
(defn attach [schema]
(->
(merge-with merge schema
{:objects {:plaid_link_result
{:fields {:token {:type 'String}} }
:plaid_item
{:fields {:external_id {:type 'String}
:id {:type :id}
:client {:type :client}
:status {:type 'String}
:last_updated {:type :iso_date}
:accounts {:type '(list :plaid_account)}}}
:plaid_item_page {:fields {:plaid_items {:type '(list :plaid_item)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:plaid_account
{:fields {:external_id {:type 'String}
:id {:type :id}
:balance {:type :money}
:name {:type 'String}
:number {:type 'String}}}}
:queries {:plaid_link_token {:type :plaid_link_result
:args {:client_id {:type :id}}
:resolve :plaid-link-token}
:plaid_item_page {:type :plaid_item_page
:args {:client_id {:type :id}
:sort {:type '(list :sort_item)}
:start {:type 'Int}
:per_page {:type 'Int}}
:resolve :get-plaid-item-page}}
:mutations {:link_plaid {:type :message
:args {:client_code {:type 'String}
:public_token {:type 'String}}
:resolve :mutation/link-plaid}
:delete_plaid_item {:type :message
:args {:id {:type :id}}
:resolve :mutation/delete-plaid-item}}})
(attach-tracing-resolvers {:plaid-link-token plaid-link-token
:get-plaid-item-page get-plaid-item-page
:mutation/link-plaid link-plaid
:mutation/delete-plaid-item delete-plaid-item})))