(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})))