Makes plaid user configurable
This commit is contained in:
@@ -157,13 +157,14 @@
|
|||||||
(when (:square_auth_token edit_client)
|
(when (:square_auth_token edit_client)
|
||||||
(square/upsert-locations (-> result :tempids (get id) (or id) d-clients/get-by-id)))
|
(square/upsert-locations (-> result :tempids (get id) (or id) d-clients/get-by-id)))
|
||||||
(let [updated-client (-> result :tempids (get id) (or id) d-clients/get-by-id)]
|
(let [updated-client (-> result :tempids (get id) (or id) d-clients/get-by-id)]
|
||||||
(solr/index-documents-raw solr/impl "clients"
|
(when (:client/name updated-client)
|
||||||
[{"id" (:db/id updated-client)
|
(solr/index-documents-raw solr/impl "clients"
|
||||||
"name" (conj (or (:client/matches updated-client) [])
|
[{"id" (:db/id updated-client)
|
||||||
(:client/name updated-client))
|
"name" (conj (or (:client/matches updated-client) [])
|
||||||
"code" (:client/code updated-client)
|
(:client/name updated-client))
|
||||||
"exact" (map str/upper-case (conj (or (:client/matches updated-client) [])
|
"code" (:client/code updated-client)
|
||||||
(:client/name updated-client)))}])
|
"exact" (map str/upper-case (conj (or (:client/matches updated-client) [])
|
||||||
|
(:client/name updated-client)))}]))
|
||||||
(-> updated-client
|
(-> updated-client
|
||||||
|
|
||||||
(update :client/bank-accounts
|
(update :client/bank-accounts
|
||||||
|
|||||||
@@ -1,130 +1,10 @@
|
|||||||
(ns auto-ap.graphql.plaid
|
(ns auto-ap.graphql.plaid
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic
|
[auto-ap.datomic :refer [conn]]
|
||||||
:refer [add-sorter-fields
|
|
||||||
apply-pagination
|
|
||||||
apply-sort-3
|
|
||||||
conn
|
|
||||||
merge-query
|
|
||||||
pull-attr
|
|
||||||
pull-many-by-id
|
|
||||||
query2]]
|
|
||||||
[auto-ap.graphql.utils
|
[auto-ap.graphql.utils
|
||||||
:refer [->graphql
|
:refer [assert-admin assert-present attach-tracing-resolvers cleanse-query]]
|
||||||
<-graphql
|
|
||||||
assert-admin
|
|
||||||
assert-can-see-client
|
|
||||||
assert-present
|
|
||||||
attach-tracing-resolvers
|
|
||||||
cleanse-query
|
|
||||||
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 dc]
|
|
||||||
[auto-ap.solr :as solr]
|
[auto-ap.solr :as solr]
|
||||||
[manifold.deferred :as de]
|
[datomic.api :as dc]))
|
||||||
[manifold.executor :as ex]))
|
|
||||||
|
|
||||||
(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 (pull-attr (dc/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) (pull-attr (dc/db conn) :db/id [:client/code (:client_code value)]))
|
|
||||||
(assert-can-see-client (:id context) (pull-attr (dc/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"}]
|
|
||||||
|
|
||||||
@(dc/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->> (query2 query)
|
|
||||||
true (apply-sort-3 args)
|
|
||||||
true (apply-pagination args))))
|
|
||||||
|
|
||||||
(defn graphql-results [ids db _]
|
|
||||||
(let [results (pull-many-by-id db default-read ids)]
|
|
||||||
(->> ids
|
|
||||||
(map results))))
|
|
||||||
|
|
||||||
(defn get-graphql [args]
|
|
||||||
(let [db (dc/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 _]
|
(defn delete-plaid-item [context args _]
|
||||||
(assert-admin (:id context))
|
(assert-admin (:id context))
|
||||||
@@ -169,28 +49,8 @@
|
|||||||
:balance {:type :money}
|
:balance {:type :money}
|
||||||
:name {:type 'String}
|
:name {:type 'String}
|
||||||
:number {:type 'String}}}}
|
:number {:type 'String}}}}
|
||||||
:queries {:plaid_link_token {:type :plaid_link_result
|
:queries {:search_plaid_merchants {:type '(list :plaid_merchant)
|
||||||
: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}
|
|
||||||
:search_plaid_merchants {:type '(list :plaid_merchant)
|
|
||||||
:args {:query {:type 'String}}
|
:args {:query {:type 'String}}
|
||||||
:resolve :search-plaid-merchants}}
|
:resolve :search-plaid-merchants}}})
|
||||||
:mutations {:link_plaid {:type :message
|
(attach-tracing-resolvers {:search-plaid-merchants search-merchants})))
|
||||||
: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
|
|
||||||
:search-plaid-merchants search-merchants
|
|
||||||
:get-plaid-item-page get-plaid-item-page
|
|
||||||
:mutation/link-plaid link-plaid
|
|
||||||
:mutation/delete-plaid-item delete-plaid-item})))
|
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,7 @@
|
|||||||
:title "1099 Vendors"
|
:title "1099 Vendors"
|
||||||
:entity-name "Vendors"
|
:entity-name "Vendors"
|
||||||
:route :company-1099-vendor-table
|
:route :company-1099-vendor-table
|
||||||
:action-buttons (fn [user]
|
:action-buttons (fn [user _]
|
||||||
nil)
|
nil)
|
||||||
:row-buttons (fn [user e]
|
:row-buttons (fn [user e]
|
||||||
[(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
|
[(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||||
|
|||||||
176
src/clj/auto_ap/ssr/company/plaid.clj
Normal file
176
src/clj/auto_ap/ssr/company/plaid.clj
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
(ns auto-ap.ssr.company.plaid
|
||||||
|
(:require
|
||||||
|
[auto-ap.datomic
|
||||||
|
:refer [add-sorter-fields
|
||||||
|
apply-pagination
|
||||||
|
apply-sort-3
|
||||||
|
conn
|
||||||
|
merge-query
|
||||||
|
pull-attr
|
||||||
|
pull-many-by-id
|
||||||
|
query2]]
|
||||||
|
[auto-ap.graphql.utils :refer [assert-can-see-client limited-clients]]
|
||||||
|
[auto-ap.logging :as alog]
|
||||||
|
[auto-ap.plaid.core :as p]
|
||||||
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
|
[auto-ap.ssr.components :as com]
|
||||||
|
[auto-ap.ssr.grid-page-helper :as helper]
|
||||||
|
[auto-ap.ssr.svg :as svg]
|
||||||
|
[auto-ap.time :as atime]
|
||||||
|
[bidi.bidi :as bidi]
|
||||||
|
[clj-time.coerce :as coerce]
|
||||||
|
[clj-time.core :as time]
|
||||||
|
[datomic.api :as dc]
|
||||||
|
[hiccup2.core :as hiccup]))
|
||||||
|
|
||||||
|
(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]]
|
||||||
|
"status" ['[?e :plaid-item/status ?sort-status]]}
|
||||||
|
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->> (query2 query)
|
||||||
|
true (apply-sort-3 args)
|
||||||
|
true (apply-pagination args))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn graphql-results [ids db _]
|
||||||
|
(let [results (pull-many-by-id db default-read ids)]
|
||||||
|
(->> ids
|
||||||
|
(map results))))
|
||||||
|
|
||||||
|
(defn get-page [args]
|
||||||
|
(let [db (dc/db conn)
|
||||||
|
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
|
||||||
|
[(graphql-results ids-to-retrieve db args)
|
||||||
|
matching-count]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn plaid-link-script [token]
|
||||||
|
(format "window.plaid = Plaid.create(
|
||||||
|
{ token: \"%s\",
|
||||||
|
onSuccess: function (x) { htmx.trigger(\"#link-account\", \"linked\", {\"public_token\": x})}
|
||||||
|
})", token))
|
||||||
|
|
||||||
|
|
||||||
|
(defn link [{{client-code "client_code" public-token "public_token"} :form-params
|
||||||
|
:keys [identity]
|
||||||
|
:as request}]
|
||||||
|
(alog/info ::linking
|
||||||
|
:request request)
|
||||||
|
(when-not client-code
|
||||||
|
(throw (ex-info "Client not provided" {:validation-error "Client not provided."})))
|
||||||
|
(when-not public-token
|
||||||
|
(throw (ex-info "Public token not provided" {:validation-error "public token not provided"})))
|
||||||
|
(alog/info ::linking-plaid :id identity :client-code client-code)
|
||||||
|
(assert-can-see-client identity (pull-attr (dc/db conn) :db/id [:client/code client-code]))
|
||||||
|
(let [access-token (:access_token (p/exchange-public-token public-token client-code))
|
||||||
|
account-result (p/get-accounts access-token )
|
||||||
|
item {:plaid-item/client [:client/code client-code]
|
||||||
|
: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"}]
|
||||||
|
|
||||||
|
@(dc/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])))
|
||||||
|
(alog/info ::access-token-was :token access-token)
|
||||||
|
{:headers {"Hx-redirect" (bidi/path-for ssr-routes/only-routes
|
||||||
|
:company-plaid)}}))
|
||||||
|
|
||||||
|
|
||||||
|
(def grid-page {:id "plaid-table"
|
||||||
|
:nav (com/company-aside-nav)
|
||||||
|
:id-fn :db/id
|
||||||
|
:fetch-page (fn [user args]
|
||||||
|
(get-page (assoc args :id user)))
|
||||||
|
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
:company)}
|
||||||
|
"My Company"]
|
||||||
|
|
||||||
|
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
:company-plaid)}
|
||||||
|
"Plaid"]]
|
||||||
|
:title "Plaid Accounts"
|
||||||
|
:entity-name "Plaid accounts"
|
||||||
|
:route :company-plaid-table
|
||||||
|
:action-buttons (fn [user params]
|
||||||
|
(when-let [client-code (:client/code (:client params))]
|
||||||
|
[[:div {:hx-post (str (bidi/path-for ssr-routes/only-routes
|
||||||
|
:company-plaid-link
|
||||||
|
:request-method :post))
|
||||||
|
:hx-vals (hiccup/raw (format "js:{client_code: \"%s\", public_token: event.detail.public_token}", client-code))
|
||||||
|
:hx-trigger "linked"}
|
||||||
|
[:script (hiccup/raw (plaid-link-script (p/get-link-token client-code)))]
|
||||||
|
(com/button {:color :primary
|
||||||
|
:id "link-account"
|
||||||
|
:onClick "window.plaid.open()"}
|
||||||
|
(com/button-icon {} svg/refresh)
|
||||||
|
"Link new account")]]))
|
||||||
|
:row-buttons (fn [user e]
|
||||||
|
[]
|
||||||
|
#_[(when (is-admin? user)
|
||||||
|
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
|
||||||
|
:company-yodlee-provider-account-refresh)
|
||||||
|
:hx-target "closest tr"}
|
||||||
|
svg/refresh))])
|
||||||
|
:headers [{:key "plaid-item"
|
||||||
|
:name "Plaid Item"
|
||||||
|
:sort-key "id"
|
||||||
|
:render :db/id}
|
||||||
|
{:key "status"
|
||||||
|
:name "Status"
|
||||||
|
:sort-key "status"
|
||||||
|
:render #(when-let [status (:plaid-item/status %)]
|
||||||
|
[:div [:div (com/pill {:color :primary }
|
||||||
|
status)]
|
||||||
|
[:div (atime/unparse-local (coerce/to-date-time (:plaid-item/last-updated %)) atime/normal-date)]])}
|
||||||
|
|
||||||
|
{:key "accounts"
|
||||||
|
:name "Accounts"
|
||||||
|
:show-starting "md"
|
||||||
|
:render (fn [e]
|
||||||
|
[:ul
|
||||||
|
(for [a (:plaid-item/accounts e)]
|
||||||
|
[:li (:plaid-account/name a) " - " (:plaid-account/number a)])])}]})
|
||||||
|
|
||||||
|
(def page (partial helper/page grid-page))
|
||||||
|
(def table (partial helper/table grid-page))
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
:title "Reports"
|
:title "Reports"
|
||||||
:entity-name "Reports"
|
:entity-name "Reports"
|
||||||
:route :company-reports-table
|
:route :company-reports-table
|
||||||
:action-buttons (fn [user]
|
:action-buttons (fn [user _]
|
||||||
nil)
|
nil)
|
||||||
:row-buttons (fn [user e]
|
:row-buttons (fn [user e]
|
||||||
(com/a-icon-button {:href (:report/url e)}
|
(com/a-icon-button {:href (:report/url e)}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ fastlink.open({fastLinkURL: '%s',
|
|||||||
:title "Yodlee Accounts"
|
:title "Yodlee Accounts"
|
||||||
:entity-name "Yodlee accounts"
|
:entity-name "Yodlee accounts"
|
||||||
:route :company-yodlee-table
|
:route :company-yodlee-table
|
||||||
:action-buttons (fn [user]
|
:action-buttons (fn [user _]
|
||||||
[(com/button {:color :primary
|
[(com/button {:color :primary
|
||||||
:on-click "openFastlink()"
|
:on-click "openFastlink()"
|
||||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
|
|||||||
@@ -249,6 +249,11 @@
|
|||||||
:href (bidi/path-for ssr-routes/only-routes
|
:href (bidi/path-for ssr-routes/only-routes
|
||||||
:company-reports)}
|
:company-reports)}
|
||||||
"Reports")]
|
"Reports")]
|
||||||
|
[:li
|
||||||
|
(menu-button- {:icon svg/bank
|
||||||
|
:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
:company-plaid)}
|
||||||
|
"Plaid Link")]
|
||||||
[:li
|
[:li
|
||||||
(menu-button- {:icon svg/bank
|
(menu-button- {:icon svg/bank
|
||||||
:href (bidi/path-for ssr-routes/only-routes
|
:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
[auto-ap.ssr.transaction.insights :as insights]
|
[auto-ap.ssr.transaction.insights :as insights]
|
||||||
[auto-ap.ssr.company.company-1099 :as company-1099]
|
[auto-ap.ssr.company.company-1099 :as company-1099]
|
||||||
[auto-ap.ssr.company.yodlee :as company-yodlee]
|
[auto-ap.ssr.company.yodlee :as company-yodlee]
|
||||||
|
[auto-ap.ssr.company.plaid :as company-plaid]
|
||||||
[auto-ap.ssr.search :as search]
|
[auto-ap.ssr.search :as search]
|
||||||
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
||||||
[auto-ap.ssr.company.reports :as company-reports]
|
[auto-ap.ssr.company.reports :as company-reports]
|
||||||
@@ -28,6 +29,9 @@
|
|||||||
:company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table))
|
:company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table))
|
||||||
:company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog))
|
:company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog))
|
||||||
:company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save))
|
:company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save))
|
||||||
|
:company-plaid (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/page))
|
||||||
|
:company-plaid-table (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/table))
|
||||||
|
:company-plaid-link (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/link))
|
||||||
:company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page))
|
:company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page))
|
||||||
:company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table))
|
:company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table))
|
||||||
:company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog))
|
:company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog))
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
))
|
))
|
||||||
"default sort"))
|
"default sort"))
|
||||||
|
|
||||||
(defn table* [grid-spec user {:keys [start per-page client flash-id sort request]}]
|
(defn table* [grid-spec user {:keys [start per-page client flash-id sort request] :as params}]
|
||||||
(let [start (or start 0)
|
(let [start (or start 0)
|
||||||
per-page (or per-page 30)
|
per-page (or per-page 30)
|
||||||
[entities total] ((:fetch-page grid-spec)
|
[entities total] ((:fetch-page grid-spec)
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
:subtitle [:div.flex.items-center.gap-2
|
:subtitle [:div.flex.items-center.gap-2
|
||||||
[:span (format "Total %s: %d, " (:entity-name grid-spec) total)]
|
[:span (format "Total %s: %d, " (:entity-name grid-spec) total)]
|
||||||
(sort-by-list sort)]
|
(sort-by-list sort)]
|
||||||
:action-buttons ((:action-buttons grid-spec) user)
|
:action-buttons ((:action-buttons grid-spec) user params)
|
||||||
:rows (for [entity entities]
|
:rows (for [entity entities]
|
||||||
(row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity))}))
|
(row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity))}))
|
||||||
:thead-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
:thead-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7/dist/_hyperscript.min.js"}]
|
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7/dist/_hyperscript.min.js"}]
|
||||||
[:script {:src "https://unpkg.com/@popperjs/core@2.11.8/dist/umd/popper.min.js"}]
|
[:script {:src "https://unpkg.com/@popperjs/core@2.11.8/dist/umd/popper.min.js"}]
|
||||||
|
[:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}]
|
||||||
#_[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
#_[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
||||||
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
||||||
:crossorigin= "anonymous"}]
|
:crossorigin= "anonymous"}]
|
||||||
|
|||||||
@@ -28,6 +28,12 @@
|
|||||||
"/table" {:get :company-yodlee-table}
|
"/table" {:get :company-yodlee-table}
|
||||||
"/fastlink" {:get :company-yodlee-fastlink-dialog}
|
"/fastlink" {:get :company-yodlee-fastlink-dialog}
|
||||||
"/refresh" {:put :company-yodlee-provider-account-refresh}}
|
"/refresh" {:put :company-yodlee-provider-account-refresh}}
|
||||||
|
|
||||||
|
"/plaid" {"" {:get :company-plaid}
|
||||||
|
"/table" {:get :company-plaid-table}
|
||||||
|
"/link" {:post :company-plaid-link}
|
||||||
|
#_#_"/fastlink" {:get :company-yodlee-fastlink-dialog}
|
||||||
|
#_#_"/refresh" {:put :company-yodlee-provider-account-refresh}}
|
||||||
}})
|
}})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
[auto-ap.views.pages.admin.users :refer [admin-users-page]]
|
[auto-ap.views.pages.admin.users :refer [admin-users-page]]
|
||||||
[auto-ap.views.pages.admin.import-batches :refer [import-batches-page]]
|
[auto-ap.views.pages.admin.import-batches :refer [import-batches-page]]
|
||||||
[auto-ap.views.pages.company.yodlee2 :as yodlee2]
|
[auto-ap.views.pages.company.yodlee2 :as yodlee2]
|
||||||
[auto-ap.views.pages.company.plaid :as plaid]
|
|
||||||
[auto-ap.views.pages.company.other :as company-other]))
|
[auto-ap.views.pages.company.other :as company-other]))
|
||||||
|
|
||||||
(defmulti page (fn [active-page] active-page))
|
(defmulti page (fn [active-page] active-page))
|
||||||
@@ -128,9 +127,6 @@
|
|||||||
(defmethod page :company-other [_]
|
(defmethod page :company-other [_]
|
||||||
(company-other/company-other-page))
|
(company-other/company-other-page))
|
||||||
|
|
||||||
(defmethod page :plaid [_]
|
|
||||||
(plaid/plaid-page))
|
|
||||||
|
|
||||||
(defmethod page :admin-accounts [_]
|
(defmethod page :admin-accounts [_]
|
||||||
(admin-accounts-page))
|
(admin-accounts-page))
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
(ns auto-ap.views.pages.admin.plaid.table
|
|
||||||
(:require
|
|
||||||
[auto-ap.status :as status]
|
|
||||||
[auto-ap.subs :as subs]
|
|
||||||
[auto-ap.views.components.buttons :as buttons]
|
|
||||||
[auto-ap.views.components.grid :as grid]
|
|
||||||
[auto-ap.views.components.modal :as modal]
|
|
||||||
[auto-ap.views.pages.data-page :as data-page]
|
|
||||||
[auto-ap.views.utils
|
|
||||||
:refer [->$ action-cell-width date->str dispatch-event with-user]]
|
|
||||||
[re-frame.core :as re-frame]))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::plaid-item-deleted
|
|
||||||
(fn [_ _]
|
|
||||||
{:dispatch [::modal/modal-closed ]}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::delete-plaid-item
|
|
||||||
[with-user ]
|
|
||||||
(fn [{:keys [user]} [_ id ]]
|
|
||||||
{:graphql {:token user
|
|
||||||
:owns-state {:single ::delete-plaid-item}
|
|
||||||
:query-obj
|
|
||||||
{:venia/operation {:operation/type :mutation
|
|
||||||
:operation/name "DeletePlaidItem"}
|
|
||||||
:venia/queries [{:query/data
|
|
||||||
[:delete-plaid-item
|
|
||||||
{:id id}
|
|
||||||
[:message]]}]}
|
|
||||||
:on-success [::plaid-item-deleted]}}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::delete-requested
|
|
||||||
[with-user]
|
|
||||||
(fn [_ [_ id]]
|
|
||||||
{:dispatch
|
|
||||||
[::modal/modal-requested {:title "Delete Provider account "
|
|
||||||
:body [:div "Are you sure you want to delete " id "?"]
|
|
||||||
:confirm {:value "Delete plaid accounts"
|
|
||||||
:status-from [::status/single ::delete-plaid-item]
|
|
||||||
:class "is-danger"
|
|
||||||
:on-click (dispatch-event [::delete-plaid-item id])
|
|
||||||
:close-event [::status/completed ::delete-plaid-item]}
|
|
||||||
:cancel? true}]}))
|
|
||||||
|
|
||||||
(re-frame/reg-sub
|
|
||||||
::params
|
|
||||||
(fn [db]
|
|
||||||
(-> db ::params)))
|
|
||||||
|
|
||||||
(defn table [{:keys [data-page]}]
|
|
||||||
(let [{:keys [data]} @(re-frame/subscribe [::data-page/page data-page])
|
|
||||||
is-admin? @(re-frame/subscribe [::subs/is-admin?])]
|
|
||||||
[grid/grid {:data-page data-page
|
|
||||||
:column-count 5}
|
|
||||||
[grid/controls data]
|
|
||||||
[grid/table {:fullwidth true}
|
|
||||||
[grid/header
|
|
||||||
[grid/row {}
|
|
||||||
[grid/header-cell {:style {:width "18em"}} "Provider Account"]
|
|
||||||
[grid/header-cell {:style {:width "12em"}} "Status"]
|
|
||||||
[grid/header-cell {:style {:width "12em"}} "Last Updated"]
|
|
||||||
[grid/header-cell {} "Accounts"]
|
|
||||||
[grid/header-cell {:style {:width (action-cell-width 1)}}]]]
|
|
||||||
[grid/body
|
|
||||||
(for [{:keys [id name accounts status last-updated] :as c} (:data data)]
|
|
||||||
^{:key (str name "-" id)}
|
|
||||||
[grid/row {:class (:class c) :id id}
|
|
||||||
[grid/cell {} id]
|
|
||||||
[grid/cell {} status]
|
|
||||||
[grid/cell {} (date->str last-updated)]
|
|
||||||
[grid/cell {}
|
|
||||||
[:ul
|
|
||||||
(for [a accounts]
|
|
||||||
^{:key (:id a)}
|
|
||||||
[:li (:name a) [:div.tag (->$ (:balance a))]])]]
|
|
||||||
[grid/cell {}
|
|
||||||
[:div.buttons
|
|
||||||
(when is-admin?
|
|
||||||
[buttons/fa-icon {:event [::delete-requested (:id c)]
|
|
||||||
:icon "fa-times"}])]]])]]
|
|
||||||
[grid/bottom-paginator data]]))
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
(ns auto-ap.views.pages.company.plaid
|
|
||||||
(:require
|
|
||||||
[auto-ap.effects.forward :as forward]
|
|
||||||
[auto-ap.status :as status]
|
|
||||||
[auto-ap.subs :as subs]
|
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
|
||||||
[auto-ap.views.pages.admin.plaid.table :as table]
|
|
||||||
[auto-ap.shared-views.company.sidebar :refer [company-side-bar]]
|
|
||||||
[auto-ap.views.pages.data-page :as data-page]
|
|
||||||
[auto-ap.views.utils :refer [dispatch-event with-user]]
|
|
||||||
[clojure.set :as set]
|
|
||||||
[re-frame.core :as re-frame]
|
|
||||||
[react-plaid-link :refer [usePlaidLink]]
|
|
||||||
[reagent.core :as reagent]
|
|
||||||
[vimsical.re-frame.fx.track :as track]))
|
|
||||||
|
|
||||||
(re-frame/reg-sub
|
|
||||||
::link-token
|
|
||||||
(fn [db]
|
|
||||||
(-> db ::link-token)))
|
|
||||||
|
|
||||||
(re-frame/reg-sub
|
|
||||||
::message
|
|
||||||
(fn [db]
|
|
||||||
(-> db ::message)))
|
|
||||||
|
|
||||||
(re-frame/reg-sub
|
|
||||||
::params
|
|
||||||
:<- [::table/params]
|
|
||||||
(fn [table-params]
|
|
||||||
table-params))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::data-requested
|
|
||||||
(fn [{:keys [db]} [_ params]]
|
|
||||||
{:graphql {:token (:user db)
|
|
||||||
:owns-state {:single ::page}
|
|
||||||
:query-obj {:venia/queries [{:query/data [:plaid-item-page {:client-id (:id @(re-frame/subscribe [::subs/client]))
|
|
||||||
:sort (:sort params)
|
|
||||||
:start (:start params 0)}
|
|
||||||
[[:plaid-items [:id :last-updated :status
|
|
||||||
[:client [:id]]
|
|
||||||
[:accounts [:id :name :number :balance]]]]
|
|
||||||
:count
|
|
||||||
:start
|
|
||||||
:end
|
|
||||||
:total]]
|
|
||||||
:query/alias :result}]
|
|
||||||
}
|
|
||||||
:on-success (fn [result]
|
|
||||||
[::data-page/received ::page
|
|
||||||
(set/rename-keys (:result result)
|
|
||||||
{:plaid-items :data})])}}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::mounted
|
|
||||||
(fn [{:keys [db]} _]
|
|
||||||
{:dispatch [::data-requested]
|
|
||||||
::forward/register {:id ::plaid-item-deleted
|
|
||||||
:events #{::table/plaid-item-deleted}
|
|
||||||
:event-fn (fn [[_ _]]
|
|
||||||
[::data-requested])}
|
|
||||||
::track/register {:id ::params
|
|
||||||
:subscription [::data-page/params ::page]
|
|
||||||
:event-fn (fn [params]
|
|
||||||
[::data-requested params])}
|
|
||||||
:db (dissoc db
|
|
||||||
::link-token
|
|
||||||
::message)}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::unmounted
|
|
||||||
(fn [_ _]
|
|
||||||
{::forward/dispose {:id ::plaid-item-deleted}
|
|
||||||
::track/dispose [{:id ::params}]}))
|
|
||||||
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::get-link-token
|
|
||||||
[with-user]
|
|
||||||
(fn [{:keys [db]} [_ _]]
|
|
||||||
{:graphql {:token (:user db)
|
|
||||||
:owns-state {:single ::get-link-token}
|
|
||||||
:query-obj {:venia/queries [[:plaid-link-token {:client-id (:id @(re-frame/subscribe [::subs/client]))}
|
|
||||||
[:token]]]}
|
|
||||||
:on-success [::authenticated]}}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::plaid-linked
|
|
||||||
(fn [{:keys [db]} [_ m]]
|
|
||||||
{:db (assoc db ::message (:message (:link-plaid m)))
|
|
||||||
:dispatch [::data-requested]}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
|
||||||
::exchange-token
|
|
||||||
[with-user]
|
|
||||||
(fn [{:keys [db]} [_ client public-token]]
|
|
||||||
{:graphql {:token (:user db)
|
|
||||||
:owns-state {:single ::get-link-token}
|
|
||||||
:query-obj
|
|
||||||
{:venia/operation {:operation/type :mutation
|
|
||||||
:operation/name "LinkPlaid"}
|
|
||||||
:venia/queries [{:query/data
|
|
||||||
[:link-plaid
|
|
||||||
{:client-code client
|
|
||||||
:public-token public-token}
|
|
||||||
[:message]]}]}
|
|
||||||
:on-success [::plaid-linked]}}))
|
|
||||||
|
|
||||||
(re-frame/reg-event-db
|
|
||||||
::authenticated
|
|
||||||
(fn [db [_ link-token]]
|
|
||||||
(-> db
|
|
||||||
(assoc-in [::link-token] (:token (:plaid-link-token link-token))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn plaid-item-table []
|
|
||||||
[table/table {:data-page ::page
|
|
||||||
:status @(re-frame/subscribe [::status/single ::page])}])
|
|
||||||
|
|
||||||
(defn link-button [{:keys [link-token client-code]}]
|
|
||||||
(let [plaid (usePlaidLink #js {:token link-token
|
|
||||||
:onSuccess (fn [x]
|
|
||||||
(re-frame/dispatch [::exchange-token client-code x]))})]
|
|
||||||
[:div
|
|
||||||
[:button.button.is-primary {:on-click (.-open plaid)}
|
|
||||||
[:span [:span.icon [:i.fa.fa-external-link]] " Go to plaid"]]]))
|
|
||||||
|
|
||||||
(defn plaid-link-token-button []
|
|
||||||
(let [status @(re-frame/subscribe [::status/single ::get-link-token])
|
|
||||||
client @(re-frame/subscribe [::subs/client])]
|
|
||||||
[:button.button.is-primary {:disabled (status/disabled-for status)
|
|
||||||
:class (status/class-for status)
|
|
||||||
:on-click (dispatch-event [::get-link-token (:code client)])}
|
|
||||||
"Authenticate with Plaid (" (:name client) ")"]))
|
|
||||||
|
|
||||||
(defn link-flow []
|
|
||||||
[:div
|
|
||||||
(let [link-token @(re-frame/subscribe [::link-token])
|
|
||||||
client-code (:code @(re-frame/subscribe [::subs/client]))]
|
|
||||||
(cond
|
|
||||||
(and link-token client-code)
|
|
||||||
[:div
|
|
||||||
"Authentication successful!"
|
|
||||||
[:f> link-button {:link-token link-token
|
|
||||||
:client-code client-code}]]
|
|
||||||
|
|
||||||
client-code
|
|
||||||
[plaid-link-token-button]
|
|
||||||
|
|
||||||
|
|
||||||
:else
|
|
||||||
nil))])
|
|
||||||
|
|
||||||
|
|
||||||
(defn admin-plaid-item-content []
|
|
||||||
(let [message @(re-frame/subscribe [::message])]
|
|
||||||
[:div
|
|
||||||
[:h1.title "Plaid Accounts"]
|
|
||||||
(when message
|
|
||||||
[:div.notification.is-info.is-light
|
|
||||||
message])
|
|
||||||
[plaid-item-table]
|
|
||||||
[link-flow]
|
|
||||||
]))
|
|
||||||
|
|
||||||
|
|
||||||
(defn plaid-page []
|
|
||||||
(reagent/create-class
|
|
||||||
{:component-will-unmount #(re-frame/dispatch [::unmounted])
|
|
||||||
:component-did-mount #(re-frame/dispatch [::mounted])
|
|
||||||
:reagent-render (fn []
|
|
||||||
[side-bar-layout {:side-bar [company-side-bar {}]
|
|
||||||
:main [admin-plaid-item-content]}])}))
|
|
||||||
|
|
||||||
|
|
||||||
@@ -132,6 +132,6 @@
|
|||||||
(:journal-entry/line-items)
|
(:journal-entry/line-items)
|
||||||
(map :journal-entry-line/dirty))))
|
(map :journal-entry-line/dirty))))
|
||||||
(is (= [true true]
|
(is (= [true true]
|
||||||
(->> (d/pull (d/db conn) '[{:journal-entry/line-items [:journal-entry-line/dirty]}] journal-entry-3)
|
(->> (d/pull (d/db conn) '[{:journal-entry/line-items [:journal-entry-line/dirty]}] journal-entry-2)
|
||||||
(:journal-entry/line-items)
|
(:journal-entry/line-items)
|
||||||
(map :journal-entry-line/dirty))))))))
|
(map :journal-entry-line/dirty))))))))
|
||||||
|
|||||||
Reference in New Issue
Block a user