Revamps all of the IOL's routing, so that the new history page can share with the rest.
This commit is contained in:
@@ -13,8 +13,6 @@
|
|||||||
org.slf4j/log4j-over-slf4j
|
org.slf4j/log4j-over-slf4j
|
||||||
org.slf4j/slf4j-nop
|
org.slf4j/slf4j-nop
|
||||||
org.slf4j/slf4j-log4j12]]
|
org.slf4j/slf4j-log4j12]]
|
||||||
[compojure "1.6.2" :exclusions [ring
|
|
||||||
ring/ring-core]]
|
|
||||||
[com.unbounce/clojure-dogstatsd-client "0.7.0"]
|
[com.unbounce/clojure-dogstatsd-client "0.7.0"]
|
||||||
[bidi "2.1.6"]
|
[bidi "2.1.6"]
|
||||||
[ring/ring-defaults "0.3.2" :exclusions [ring ring/ring-core]]
|
[ring/ring-defaults "0.3.2" :exclusions [ring ring/ring-core]]
|
||||||
|
|||||||
@@ -2,25 +2,25 @@
|
|||||||
(:require
|
(:require
|
||||||
[amazonica.core :refer [defcredential]]
|
[amazonica.core :refer [defcredential]]
|
||||||
[auto-ap.client-routes :as client-routes]
|
[auto-ap.client-routes :as client-routes]
|
||||||
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
|
[auto-ap.logging :as alog]
|
||||||
[auto-ap.routes.auth :as auth]
|
[auto-ap.routes.auth :as auth]
|
||||||
[auto-ap.routes.exports :as exports]
|
[auto-ap.routes.exports :as exports]
|
||||||
[auto-ap.routes.ezcater :as ezcater]
|
[auto-ap.routes.ezcater :as ezcater]
|
||||||
[auto-ap.routes.graphql :as graphql]
|
[auto-ap.routes.graphql :as graphql]
|
||||||
|
[auto-ap.routes.health :as health]
|
||||||
[auto-ap.routes.invoices :as invoices]
|
[auto-ap.routes.invoices :as invoices]
|
||||||
[auto-ap.routes.queries :as queries]
|
[auto-ap.routes.queries :as queries]
|
||||||
[auto-ap.routes.yodlee2 :as yodlee2]
|
[auto-ap.routes.yodlee2 :as yodlee2]
|
||||||
[auto-ap.ssr.admin :as ssr-admin]
|
[auto-ap.ssr.core :as ssr]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
|
[bidi.ring :refer [->ResourcesMaybe make-handler]]
|
||||||
[buddy.auth.backends.session :refer [session-backend]]
|
[buddy.auth.backends.session :refer [session-backend]]
|
||||||
[buddy.auth.backends.token :refer [jws-backend]]
|
[buddy.auth.backends.token :refer [jws-backend]]
|
||||||
[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
|
[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
|
||||||
|
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.logging :as log]
|
[com.brunobonacci.mulog :as mu]
|
||||||
[compojure.core :refer [ANY context defroutes GET routes]]
|
|
||||||
[compojure.route :as route]
|
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[mount.core :as mount]
|
|
||||||
[ring.middleware.edn :refer [wrap-edn-params]]
|
[ring.middleware.edn :refer [wrap-edn-params]]
|
||||||
[ring.middleware.multipart-params :as mp]
|
[ring.middleware.multipart-params :as mp]
|
||||||
[ring.middleware.params :refer [wrap-params]]
|
[ring.middleware.params :refer [wrap-params]]
|
||||||
@@ -33,80 +33,129 @@
|
|||||||
(when (:aws-access-key-id env)
|
(when (:aws-access-key-id env)
|
||||||
(defcredential (:aws-access-key-id env) (:aws-secret-access-key env) (:aws-region env)))
|
(defcredential (:aws-access-key-id env) (:aws-secret-access-key env) (:aws-region env)))
|
||||||
|
|
||||||
(def running? (atom false))
|
(defn deep-merge [v & vs]
|
||||||
|
(letfn [(rec-merge [v1 v2]
|
||||||
|
(if (and (map? v1) (map? v2))
|
||||||
|
(merge-with deep-merge v1 v2)
|
||||||
|
v2))]
|
||||||
|
(when (some identity vs)
|
||||||
|
(reduce #(rec-merge %1 %2) v vs))))
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
(def all-routes ["/" (-> (into []
|
||||||
(mount/defstate manage-running?
|
(deep-merge ssr-routes/routes
|
||||||
:start (reset! running? true)
|
(second client-routes/routes)
|
||||||
:stop (reset! running? false))
|
graphql/routes
|
||||||
|
ezcater/routes
|
||||||
|
health/routes
|
||||||
|
queries/routes2
|
||||||
|
yodlee2/routes
|
||||||
|
auth/routes
|
||||||
|
invoices/routes
|
||||||
|
exports/routes2))
|
||||||
|
(conj ["" (->ResourcesMaybe {:prefix "public/"})])
|
||||||
|
(conj [true :not-found])) ;; always go for not found as last resort, have to switch to vec in order for that to work
|
||||||
|
])
|
||||||
|
|
||||||
(defroutes static-routes
|
(defn not-found [_]
|
||||||
(GET "/" [] (response/resource-response "index.html" {:root "public"}))
|
{:status 404
|
||||||
|
:headers {}
|
||||||
(route/resources "/")
|
:body ""})
|
||||||
(routes (ANY "*" {:keys [uri]}
|
|
||||||
(if (bidi/match-route client-routes/routes uri)
|
|
||||||
(response/resource-response "index.html" {:root "public"})
|
|
||||||
{:status 404
|
|
||||||
:body "Not found"}))))
|
|
||||||
|
|
||||||
(defroutes health-check
|
(defn render-index [_]
|
||||||
(GET "/health-check" []
|
(response/resource-response "index.html" {:root "public"}))
|
||||||
(if @running?
|
|
||||||
{:status 200
|
(def match->handler-lookup
|
||||||
:body "Ok"}
|
(-> {:not-found not-found}
|
||||||
{:status 503
|
(merge ssr/key->handler)
|
||||||
:body "Application shut down"})))
|
(merge graphql/match->handler)
|
||||||
|
(merge ezcater/match->handler)
|
||||||
|
(merge health/match->handler)
|
||||||
|
(merge queries/match->handler)
|
||||||
|
(merge yodlee2/match->handler)
|
||||||
|
(merge auth/match->handler)
|
||||||
|
(merge invoices/match->handler)
|
||||||
|
(merge exports/match->handler)
|
||||||
|
(merge
|
||||||
|
(into {}
|
||||||
|
(map
|
||||||
|
|
||||||
|
(fn [k]
|
||||||
|
[k render-index])
|
||||||
|
client-routes/all-matches)))))
|
||||||
|
|
||||||
|
(def match->handler
|
||||||
|
(fn [route]
|
||||||
|
(or (get match->handler-lookup route)
|
||||||
|
route)))
|
||||||
|
|
||||||
|
|
||||||
|
(def route-handler
|
||||||
|
(make-handler all-routes
|
||||||
|
match->handler))
|
||||||
|
|
||||||
|
(defn wrap-guess-route [handler]
|
||||||
|
(fn [{:keys [uri request-method] :as request} ]
|
||||||
|
(let [matched-route (:handler
|
||||||
|
(bidi.bidi/match-route all-routes
|
||||||
|
uri
|
||||||
|
:request-method request-method))]
|
||||||
|
(handler (assoc request
|
||||||
|
:matched-route
|
||||||
|
matched-route)))))
|
||||||
|
|
||||||
|
(defn test-match-route [method uri]
|
||||||
|
(bidi.bidi/match-route all-routes
|
||||||
|
uri
|
||||||
|
:request-method method))
|
||||||
|
|
||||||
(defroutes api-routes
|
|
||||||
(context "/api" []
|
|
||||||
exports/export-routes
|
|
||||||
yodlee2/routes
|
|
||||||
queries/query2-routes
|
|
||||||
invoices/routes
|
|
||||||
graphql/routes
|
|
||||||
ezcater/routes
|
|
||||||
auth/routes
|
|
||||||
health-check))
|
|
||||||
|
|
||||||
(def auth-backend (jws-backend {:secret (:jwt-secret env) :options {:alg :hs512}}))
|
(def auth-backend (jws-backend {:secret (:jwt-secret env) :options {:alg :hs512}}))
|
||||||
|
|
||||||
(defn wrap-transaction [handler]
|
|
||||||
(fn [request]
|
|
||||||
(handler request)))
|
|
||||||
|
|
||||||
(def app-routes
|
|
||||||
(routes
|
|
||||||
(wrap-transaction api-routes)
|
|
||||||
ssr-admin/admin-routes
|
|
||||||
static-routes))
|
|
||||||
|
|
||||||
(defn wrap-logging [handler]
|
(defn wrap-logging [handler]
|
||||||
(fn [request]
|
(fn [request]
|
||||||
(lc/with-context {:uri (:uri request)
|
(mu/with-context {:uri (:uri request)
|
||||||
:source "request"
|
:query (:uri request)
|
||||||
|
:request-method (:request-method request)
|
||||||
|
:user (:identity request)
|
||||||
:user-role (:user/role (:identity request))
|
:user-role (:user/role (:identity request))
|
||||||
:user-name (:user/name (:identity request))}
|
:user-name (:user/name (:identity request))}
|
||||||
|
(mu/trace ::http-request-trace
|
||||||
|
[]
|
||||||
|
(lc/with-context {:uri (:uri request)
|
||||||
|
:source "request"
|
||||||
|
:user-role (:user/role (:identity request))
|
||||||
|
:user-name (:user/name (:identity request))}
|
||||||
|
|
||||||
|
|
||||||
(when-not (str/includes? (:uri request) "health-check")
|
(when-not (str/includes? (:uri request) "health-check")
|
||||||
(log/info "Beginning request" (:uri request)))
|
(alog/info ::http-request-starting))
|
||||||
(handler request))))
|
(try
|
||||||
|
(let [response (handler request)]
|
||||||
|
(alog/info ::http-request-done
|
||||||
|
:status-code (:status response))
|
||||||
|
response)
|
||||||
|
(catch Exception e
|
||||||
|
(alog/error ::request-error
|
||||||
|
:exception e)
|
||||||
|
(throw e))))))))
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(def app
|
(def app
|
||||||
(-> #'app-routes
|
(-> route-handler
|
||||||
(wrap-logging)
|
(wrap-guess-route)
|
||||||
(wrap-authorization auth-backend
|
(wrap-authorization auth-backend
|
||||||
)
|
)
|
||||||
(wrap-authentication auth-backend
|
(wrap-authentication auth-backend
|
||||||
(session-backend {:authfn (fn [auth]
|
(session-backend {:authfn (fn [auth]
|
||||||
(dissoc auth :exp))}))
|
(dissoc auth :exp))}))
|
||||||
|
|
||||||
(wrap-session {:store (ring.middleware.session.cookie/cookie-store
|
(wrap-session {:store (cookie-store
|
||||||
{:key
|
{:key
|
||||||
(byte-array
|
(byte-array
|
||||||
[42, 52, -31, 105, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])} )})
|
[42, 52, -31, 105, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])} )})
|
||||||
(wrap-reload)
|
(wrap-reload)
|
||||||
(wrap-params)
|
(wrap-params)
|
||||||
(mp/wrap-multipart-params)
|
(mp/wrap-multipart-params)
|
||||||
(wrap-edn-params)))
|
(wrap-edn-params)
|
||||||
|
(wrap-logging)))
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
(ns auto-ap.routes.auth
|
(ns auto-ap.routes.auth
|
||||||
(:require [auto-ap.datomic.users :as users]
|
(:require
|
||||||
[buddy.sign.jwt :as jwt]
|
[auto-ap.datomic.users :as users]
|
||||||
[clj-http.client :as http]
|
[buddy.sign.jwt :as jwt]
|
||||||
[clj-time.core :as time]
|
[clj-http.client :as http]
|
||||||
[compojure.core :refer [GET defroutes]]
|
[clj-time.core :as time]
|
||||||
[config.core :refer [env]]
|
[clojure.tools.logging :as log]
|
||||||
[clojure.tools.logging :as log]))
|
[config.core :refer [env]]))
|
||||||
|
|
||||||
(def google-client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com")
|
(def google-client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com")
|
||||||
(def google-client-secret "OC-WemHurPXYpuIw5cT-B90g")
|
(def google-client-secret "OC-WemHurPXYpuIw5cT-B90g")
|
||||||
@@ -19,48 +19,50 @@
|
|||||||
(:jwt-secret env)
|
(:jwt-secret env)
|
||||||
{:alg :hs512}))
|
{:alg :hs512}))
|
||||||
|
|
||||||
(defroutes routes
|
(defn oauth [{{:strs [code]} :query-params {:strs [host]} :headers}]
|
||||||
(GET "/oauth" {{:strs [code]} :query-params {:strs [host]} :headers}
|
(try
|
||||||
(try
|
(let [auth (-> "https://accounts.google.com/o/oauth2/token"
|
||||||
(let [auth (-> "https://accounts.google.com/o/oauth2/token"
|
(http/post
|
||||||
(http/post
|
{:form-params {"client_id" google-client-id
|
||||||
{:form-params {"client_id" google-client-id
|
"client_secret" google-client-secret
|
||||||
"client_secret" google-client-secret
|
"code" code
|
||||||
"code" code
|
"redirect_uri" (str (:scheme env) "://" host "/api/oauth")
|
||||||
"redirect_uri" (str (:scheme env) "://" host "/api/oauth")
|
"grant_type" "authorization_code"}
|
||||||
"grant_type" "authorization_code"}
|
:as :json})
|
||||||
:as :json})
|
:body)
|
||||||
:body)
|
token (:access_token auth)
|
||||||
token (:access_token auth)
|
profile (-> (http/get "https://www.googleapis.com/oauth2/v1/userinfo"
|
||||||
profile (-> (http/get "https://www.googleapis.com/oauth2/v1/userinfo"
|
{:headers {"Authorization" (str "Bearer " token)} :as :json})
|
||||||
{:headers {"Authorization" (str "Bearer " token)} :as :json})
|
:body)
|
||||||
:body)
|
user (users/find-or-insert! {:user/provider "google"
|
||||||
user (users/find-or-insert! {:user/provider "google"
|
:user/provider-id (:id profile)
|
||||||
:user/provider-id (:id profile)
|
:user/role :user-role/none
|
||||||
:user/role :user-role/none
|
:user/name (:name profile)})
|
||||||
:user/name (:name profile)})
|
auth {:user (:name profile)
|
||||||
auth {:user (:name profile)
|
:exp (time/plus (time/now) (time/days 30))
|
||||||
:exp (time/plus (time/now) (time/days 30))
|
:user/clients (map (fn [c]
|
||||||
:user/clients (map (fn [c]
|
(select-keys c [:client/code :db/id :client/name :client/locations]))
|
||||||
(select-keys c [:client/code :db/id :client/name :client/locations]))
|
(:user/clients user))
|
||||||
(:user/clients user))
|
:user/role (name (:user/role user))
|
||||||
:user/role (name (:user/role user))
|
:user/name (:name profile)}
|
||||||
:user/name (:name profile)}
|
]
|
||||||
]
|
(log/info "authenticated as user" user)
|
||||||
(log/info "authenticated as user" user)
|
;; TODO - these namespaces are not being transmitted/deserialized properly
|
||||||
;; TODO - these namespaces are not being transmitted/deserialized properly
|
|
||||||
|
(if (and token user)
|
||||||
(if (and token user)
|
(let [jwt (jwt/sign auth
|
||||||
(let [jwt (jwt/sign auth
|
(:jwt-secret env)
|
||||||
(:jwt-secret env)
|
{:alg :hs512})]
|
||||||
{:alg :hs512})]
|
|
||||||
|
{:status 301
|
||||||
{:status 301
|
:headers {"Location" (str "/?jwt=" jwt)}
|
||||||
:headers {"Location" (str "/?jwt=" jwt)}
|
:session {:identity (dissoc auth :exp)}})
|
||||||
:session {:identity (dissoc auth :exp)}})
|
{:status 401
|
||||||
{:status 401
|
:body "Couldn't authenticate"}))
|
||||||
:body "Couldn't authenticate"}))
|
(catch Exception e
|
||||||
(catch Exception e
|
(log/warn e )
|
||||||
(log/warn e )
|
{:status 401
|
||||||
{:status 401
|
:body (str "Couldn't authenticate " (.toString e))})))
|
||||||
:body (str "Couldn't authenticate " (.toString e))}))))
|
|
||||||
|
(def routes {"api" {"/oauth" :oauth}})
|
||||||
|
(def match->handler {:oauth oauth})
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
[auto-ap.routes.utils :refer [wrap-secure]]
|
[auto-ap.routes.utils :refer [wrap-secure]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[buddy.sign.jwt :as jwt]
|
[buddy.sign.jwt :as jwt]
|
||||||
|
[cheshire.generate :as generate]
|
||||||
[clj-time.coerce :as coerce :refer [to-date]]
|
[clj-time.coerce :as coerce :refer [to-date]]
|
||||||
[clj-time.core :as time]
|
[clj-time.core :as time]
|
||||||
[clojure.data.csv :as csv]
|
[clojure.data.csv :as csv]
|
||||||
[clojure.edn :refer [read-string]]
|
[clojure.edn :refer [read-string]]
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[com.unbounce.dogstatsd.core :as statsd]
|
[com.unbounce.dogstatsd.core :as statsd]
|
||||||
[compojure.core :refer [context defroutes GET routes wrap-routes]]
|
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.api :as d]
|
[datomic.api :as d]
|
||||||
[ring.middleware.json :refer [wrap-json-response]]
|
[ring.middleware.json :refer [wrap-json-response]]
|
||||||
@@ -30,25 +30,25 @@
|
|||||||
(csv/write-csv w %)
|
(csv/write-csv w %)
|
||||||
(.toString w))))))
|
(.toString w))))))
|
||||||
|
|
||||||
(def api-key-authed-routes
|
(defn aggregated-sales-export [{:keys [query-params]}]
|
||||||
(context "/" []
|
(let [client-id (Long/parseLong (get query-params "client-id"))
|
||||||
(GET "/sales/aggregated/export" {:keys [query-params]}
|
identity (jwt/unsign (get query-params "key") (:jwt-secret env) {:alg :hs512})]
|
||||||
(let [client-id (Long/parseLong (get query-params "client-id"))
|
(assert-can-see-client identity client-id)
|
||||||
identity (jwt/unsign (get query-params "key") (:jwt-secret env) {:alg :hs512})]
|
(into (list)
|
||||||
(assert-can-see-client identity client-id)
|
(d/query {:query {:find '[?d4 (sum ?total) (sum ?tax) (sum ?tip) (sum ?service-charge)]
|
||||||
(into (list)
|
:in '[$ ?c]
|
||||||
(d/query {:query {:find '[?d4 (sum ?total) (sum ?tax) (sum ?tip) (sum ?service-charge)]
|
:where '[[?s :sales-order/client ?c]
|
||||||
:in '[$ ?c]
|
[?s :sales-order/date ?d]
|
||||||
:where '[[?s :sales-order/client ?c]
|
[?s :sales-order/total ?total]
|
||||||
[?s :sales-order/date ?d]
|
[?s :sales-order/tax ?tax]
|
||||||
[?s :sales-order/total ?total]
|
[?s :sales-order/tip ?tip]
|
||||||
[?s :sales-order/tax ?tax]
|
[?s :sales-order/service-charge ?service-charge]
|
||||||
[?s :sales-order/tip ?tip]
|
[(clj-time.coerce/to-date-time ?d) ?d2]
|
||||||
[?s :sales-order/service-charge ?service-charge]
|
[(auto-ap.time/localize ?d2) ?d3]
|
||||||
[(clj-time.coerce/to-date-time ?d) ?d2]
|
[(auto-ap.time/unparse ?d3 auto-ap.time/normal-date) ?d4]]}
|
||||||
[(auto-ap.time/localize ?d2) ?d3]
|
:args [(d/db conn) client-id]}))))
|
||||||
[(auto-ap.time/unparse ?d3 auto-ap.time/normal-date) ?d4]]}
|
|
||||||
:args [(d/db conn) client-id]}))))))
|
|
||||||
|
|
||||||
(defn client-tag [params]
|
(defn client-tag [params]
|
||||||
(when-let [code (or (params "client-code")
|
(when-let [code (or (params "client-code")
|
||||||
@@ -82,332 +82,366 @@
|
|||||||
]))
|
]))
|
||||||
m))
|
m))
|
||||||
|
|
||||||
(def admin-only-routes
|
(defn export-invoices [{:keys [query-params identity]}]
|
||||||
(context "/" []
|
(assert-admin identity)
|
||||||
(GET "/invoices/export" {:keys [query-params identity]}
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
(assert-admin identity)
|
"export:invoice"}}]
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
{:body
|
||||||
"export:invoice"}}]
|
(list (into (list)
|
||||||
(list (into (list)
|
(map datomic-map->graphql-map)
|
||||||
(map datomic-map->graphql-map)
|
(d/q '[:find [(pull ?i [:db/id :invoice/total :invoice/outstanding-balance :invoice/invoice-number :invoice/date :invoice/original-id
|
||||||
(d/q '[:find [(pull ?i [:db/id :invoice/total :invoice/outstanding-balance :invoice/invoice-number :invoice/date :invoice/original-id
|
{ :invoice/status [:db/ident]
|
||||||
{ :invoice/status [:db/ident]
|
:invoice/payments
|
||||||
:invoice/payments
|
[:invoice-payment/amount
|
||||||
[:invoice-payment/amount
|
{:invoice-payment/payment [:payment/check-number
|
||||||
{:invoice-payment/payment [:payment/check-number
|
:payment/memo
|
||||||
:payment/memo
|
{:payment/bank_account [:bank-account/id :bank-account/name :bank-account/number :bank-account/bank-name :bank-account/bank-code :bank-account/code]}]}]
|
||||||
{:payment/bank_account [:bank-account/id :bank-account/name :bank-account/number :bank-account/bank-name :bank-account/bank-code :bank-account/code]}]}]
|
:invoice/vendor [:vendor/name
|
||||||
:invoice/vendor [:vendor/name
|
:db/id
|
||||||
:db/id
|
{:vendor/primary-contact [:contact/name]
|
||||||
{:vendor/primary-contact [:contact/name]
|
:vendor/address [:address/street1 :address/city :address/state :address/zip]}]
|
||||||
:vendor/address [:address/street1 :address/city :address/state :address/zip]}]
|
:invoice/expense-accounts [:db/id
|
||||||
:invoice/expense-accounts [:db/id
|
:invoice-expense-account/amount
|
||||||
:invoice-expense-account/amount
|
:invoice-expense-account/id
|
||||||
:invoice-expense-account/id
|
:invoice-expense-account/location
|
||||||
:invoice-expense-account/location
|
{:invoice-expense-account/account
|
||||||
{:invoice-expense-account/account
|
[:db/id :account/numeric-code :account/name]}]
|
||||||
[:db/id :account/numeric-code :account/name]}]
|
:invoice/client [:client/name :db/id :client/code :client/locations]}]) ...]
|
||||||
:invoice/client [:client/name :db/id :client/code :client/locations]}]) ...]
|
:in $ ?c
|
||||||
:in $ ?c
|
:where [?i :invoice/client ?c]]
|
||||||
:where [?i :invoice/client ?c]]
|
(d/db conn)
|
||||||
(d/db conn)
|
[:client/code (query-params "client-code")])))}))
|
||||||
[:client/code (query-params "client-code")])))))
|
|
||||||
(GET "/payments/export" {:keys [query-params identity]}
|
|
||||||
(assert-admin identity)
|
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
|
||||||
"export:payment"}}]
|
|
||||||
(let [query [[:all_payments
|
|
||||||
{:client-code (query-params "client-code")
|
|
||||||
:original-id (query-params "original")}
|
|
||||||
[:id :check-number :amount :memo :date :status :type :original-id
|
|
||||||
[:invoices [[:invoice [:id :original-id]] :amount]]
|
|
||||||
[:bank-account [:number :code :bank-name :bank-code :id]]
|
|
||||||
[:vendor [:name :id [:primary-contact [:name :email :phone]] [:default-account [:name :numeric-code :id]] [:address [:street1 :city :state :zip]]]]
|
|
||||||
[:client [:id :name :code]]
|
|
||||||
]]]
|
|
||||||
payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
|
|
||||||
(list (:all-payments (:data payments))))))
|
|
||||||
|
|
||||||
(GET "/sales/export" {:keys [query-params identity]}
|
(defn export-payments [{:keys [query-params identity]}]
|
||||||
(assert-admin identity)
|
(assert-admin identity)
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
"export:sales"}}]
|
"export:payment"}}]
|
||||||
(let [query [[:all_sales_orders
|
(let [query [[:all_payments
|
||||||
(cond-> {:client-code (query-params "client-code")}
|
{:client-code (query-params "client-code")
|
||||||
(query-params "after") (assoc :date-range {:start (query-params "after")
|
:original-id (query-params "original")}
|
||||||
:end nil}))
|
[:id :check-number :amount :memo :date :status :type :original-id
|
||||||
[:id
|
[:invoices [[:invoice [:id :original-id]] :amount]]
|
||||||
:location
|
[:bank-account [:number :code :bank-name :bank-code :id]]
|
||||||
:external_id
|
[:vendor [:name :id [:primary-contact [:name :email :phone]] [:default-account [:name :numeric-code :id]] [:address [:street1 :city :state :zip]]]]
|
||||||
:total
|
[:client [:id :name :code]]
|
||||||
:tip
|
]]]
|
||||||
:tax
|
payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
|
||||||
:discount
|
{:body
|
||||||
:returns
|
(list (:all-payments (:data payments)))})))
|
||||||
:service_charge
|
|
||||||
:date
|
|
||||||
[:charges [:type_name :total :tip]]
|
|
||||||
[:line_items [:item_name :total :tax :discount :category]]
|
|
||||||
[:client [:id :name :code]]]]]
|
|
||||||
payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))
|
|
||||||
parsedouble #(some-> % Double/parseDouble) ]
|
|
||||||
(seq (map
|
|
||||||
(fn [s]
|
|
||||||
(-> s
|
|
||||||
(assoc :utc_date (:date s))
|
|
||||||
(update :date (fn [d]
|
|
||||||
(coerce/to-string (coerce/to-local-date-time (time/to-time-zone (coerce/to-date-time d) (time/time-zone-for-id "America/Los_Angeles"))))))
|
|
||||||
(update :total parsedouble)
|
|
||||||
(update :tax parsedouble)
|
|
||||||
(update :discount parsedouble)
|
|
||||||
(update :tip parsedouble)
|
|
||||||
(update :line-items (fn [lis]
|
|
||||||
(map
|
|
||||||
(fn [li]
|
|
||||||
(-> li
|
|
||||||
(update :tax parsedouble)
|
|
||||||
(update :discount parsedouble)
|
|
||||||
(update :total parsedouble)))
|
|
||||||
lis)))
|
|
||||||
(update :charges (fn [charges]
|
|
||||||
(map
|
|
||||||
(fn [charge]
|
|
||||||
(-> charge
|
|
||||||
(update :tip parsedouble)
|
|
||||||
(update :total parsedouble)))
|
|
||||||
charges)))))
|
|
||||||
(:all-sales-orders (:data payments))))))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(GET "/expected-deposit/export" {:keys [query-params identity]}
|
|
||||||
(assert-admin identity)
|
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
|
||||||
"export:deposit"}}]
|
|
||||||
(let [query [[:all_expected_deposits
|
|
||||||
(cond-> {:client-code (query-params "client-code")}
|
|
||||||
(query-params "after") (assoc :date-range {:start (query-params "after")
|
|
||||||
:end nil}))
|
|
||||||
[:id
|
|
||||||
[:client [:id :name :code]]
|
|
||||||
:location
|
|
||||||
:external_id
|
|
||||||
:total
|
|
||||||
:fee
|
|
||||||
:date]]]
|
|
||||||
payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
|
|
||||||
(seq (map
|
|
||||||
(fn [d]
|
|
||||||
(-> d
|
|
||||||
(update :fee #(some-> % Double/parseDouble))
|
|
||||||
(update :total #(some-> % Double/parseDouble))))
|
|
||||||
(:all-expected-deposits (:data payments)))))))
|
|
||||||
(GET "/clients/export" {:keys [identity]}
|
|
||||||
(assert-admin identity)
|
|
||||||
(map <-graphql (d-clients/get-all)))
|
|
||||||
|
|
||||||
(GET "/vendors/export" {:keys [identity]}
|
|
||||||
(assert-admin identity)
|
|
||||||
(statsd/time! [(str "export.time") {:tags #{"export:vendors"}}]
|
|
||||||
(map <-graphql (->> (d/q '[:find [?e ...]
|
|
||||||
:in $
|
|
||||||
:where [?e :vendor/name]]
|
|
||||||
(d/db conn))
|
|
||||||
(d/pull-many (d/db conn) vendor/default-read)))))
|
|
||||||
|
|
||||||
(GET "/vendors/company/export" {:keys [identity query-params]}
|
|
||||||
(statsd/time! [(str "export.time") {:tags #{"export:company-vendors"}}]
|
|
||||||
(let [client (:db/id (d/pull (d/db conn) [:db/id] [:client/code (get query-params "client")]))
|
|
||||||
|
|
||||||
_ (assert-can-see-client identity client)
|
|
||||||
data (->> (d/q '[:find (pull ?v [:vendor/name
|
|
||||||
:vendor/terms
|
|
||||||
{:vendor/default-account [:account/name :account/numeric-code
|
|
||||||
{:account/client-overrides
|
|
||||||
[:account-client-override/client
|
|
||||||
:account-client-override/name]}]
|
|
||||||
:vendor/terms-overrides [:vendor-terms-override/client
|
|
||||||
:vendor-terms-override/terms]
|
|
||||||
:vendor/account-overrides [:vendor-account-override/client
|
|
||||||
{:vendor-account-override/account [:account/numeric-code :account/name
|
|
||||||
{:account/client-overrides
|
|
||||||
[:account-client-override/client
|
|
||||||
:account-client-override/name]}]}]
|
|
||||||
:vendor/address [:address/street1 :address/city :address/state :address/zip]}])
|
|
||||||
:in $ ?c
|
|
||||||
:where [?vu :vendor-usage/client ?c]
|
|
||||||
[?vu :vendor-usage/count ?count]
|
|
||||||
[(>= ?vu 0)]
|
|
||||||
[?vu :vendor-usage/vendor ?v]
|
|
||||||
(not [?v :vendor/hidden true])]
|
|
||||||
(d/db conn)
|
|
||||||
client)
|
|
||||||
(map (fn [[v]]
|
|
||||||
[(-> v :vendor/name)
|
|
||||||
(-> v :vendor/address :address/street1)
|
|
||||||
(-> v :vendor/address :address/city)
|
|
||||||
(-> v :vendor/address :address/state)
|
|
||||||
(-> v :vendor/address :address/zip)
|
|
||||||
(-> v (vendor/terms-for-client-id client) )
|
|
||||||
(-> v (vendor/account-for-client-id client) (accounts/clientize client) :account/name)
|
|
||||||
(-> v (vendor/account-for-client-id client) :account/numeric-code)
|
|
||||||
]
|
|
||||||
))
|
|
||||||
(into [["Vendor Name" "Address" "City" "State" "Zip" "Terms" "Account" "Account Code"]]))]
|
|
||||||
{:body
|
|
||||||
(with-open [w (java.io.StringWriter.)]
|
|
||||||
(csv/write-csv w data
|
|
||||||
:quote? (constantly true))
|
|
||||||
(.toString w))
|
|
||||||
:headers {"Content-Type" "application/csv"}})))
|
|
||||||
(GET "/ledger/export" {:keys [identity query-params]}
|
|
||||||
(let [start-date (or (some-> (query-params "start-date")
|
|
||||||
(atime/parse atime/iso-date))
|
|
||||||
(time/plus (time/now) (time/days -120)))]
|
|
||||||
(log/info "exporting for " (query-params "client-code") "starting" start-date)
|
|
||||||
(assert-admin identity)
|
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
|
||||||
"export:ledger2"}}]
|
|
||||||
(let [results (->> (d/q '[:find (pull ?e [:db/id
|
|
||||||
:journal-entry/external-id
|
|
||||||
:journal-entry/cleared
|
|
||||||
:journal-entry/alternate-description
|
|
||||||
:journal-entry/date
|
|
||||||
:journal-entry/note
|
|
||||||
:journal-entry/amount
|
|
||||||
:journal-entry/source
|
|
||||||
:journal-entry/cleared-against
|
|
||||||
:journal-entry/original-entity
|
|
||||||
{:journal-entry/client [:client/name :client/code :db/id]
|
|
||||||
:journal-entry/vendor [:vendor/name :db/id]
|
|
||||||
:journal-entry/line-items [:db/id
|
|
||||||
:journal-entry-line/location
|
|
||||||
:journal-entry-line/debit
|
|
||||||
:journal-entry-line/credit
|
|
||||||
{:journal-entry-line/account [:bank-account/include-in-reports
|
|
||||||
:bank-account/bank-name
|
|
||||||
:bank-account/numeric-code
|
|
||||||
:bank-account/code
|
|
||||||
:bank-account/visible
|
|
||||||
:bank-account/name
|
|
||||||
:bank-account/number
|
|
||||||
:account/code
|
|
||||||
:account/name
|
|
||||||
:account/numeric-code
|
|
||||||
:account/location
|
|
||||||
{:account/type [:db/ident :db/id]}
|
|
||||||
{:bank-account/type [:db/ident :db/id]}]}]}])
|
|
||||||
:in $ ?c ?start-date
|
|
||||||
:where [?e :journal-entry/client ?c]
|
|
||||||
[?e :journal-entry/date ?date]
|
|
||||||
[(>= ?date ?start-date)]]
|
|
||||||
(d/db conn)
|
|
||||||
[:client/code (query-params "client-code")]
|
|
||||||
(coerce/to-date start-date)))
|
|
||||||
tf-result (transduce (comp
|
|
||||||
(map first)
|
|
||||||
(filter (fn [je]
|
|
||||||
(every?
|
|
||||||
(fn [jel]
|
|
||||||
(let [include-in-reports (-> jel :journal-entry-line/account :bank-account/include-in-reports)]
|
|
||||||
(or (nil? include-in-reports)
|
|
||||||
(true? include-in-reports))))
|
|
||||||
(:journal-entry/line-items je))))
|
|
||||||
(map <-graphql))
|
|
||||||
conj
|
|
||||||
(list)
|
|
||||||
results)]
|
|
||||||
tf-result))))
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn export-sales [{:keys [query-params identity]}]
|
||||||
|
(assert-admin identity)
|
||||||
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
|
"export:sales"}}]
|
||||||
|
(let [query [[:all_sales_orders
|
||||||
|
(cond-> {:client-code (query-params "client-code")}
|
||||||
|
(query-params "after") (assoc :date-range {:start (query-params "after")
|
||||||
|
:end nil}))
|
||||||
|
[:id
|
||||||
|
:location
|
||||||
|
:external_id
|
||||||
|
:total
|
||||||
|
:tip
|
||||||
|
:tax
|
||||||
|
:discount
|
||||||
|
:returns
|
||||||
|
:service_charge
|
||||||
|
:date
|
||||||
|
[:charges [:type_name :total :tip]]
|
||||||
|
[:line_items [:item_name :total :tax :discount :category]]
|
||||||
|
[:client [:id :name :code]]]]]
|
||||||
|
payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))
|
||||||
|
parsedouble #(some-> % Double/parseDouble) ]
|
||||||
|
{:body
|
||||||
|
(seq (map
|
||||||
|
(fn [s]
|
||||||
|
(-> s
|
||||||
|
(assoc :utc_date (:date s))
|
||||||
|
(update :date (fn [d]
|
||||||
|
(coerce/to-string (coerce/to-local-date-time (time/to-time-zone (coerce/to-date-time d) (time/time-zone-for-id "America/Los_Angeles"))))))
|
||||||
|
(update :total parsedouble)
|
||||||
|
(update :tax parsedouble)
|
||||||
|
(update :discount parsedouble)
|
||||||
|
(update :tip parsedouble)
|
||||||
|
(update :line-items (fn [lis]
|
||||||
|
(map
|
||||||
|
(fn [li]
|
||||||
|
(-> li
|
||||||
|
(update :tax parsedouble)
|
||||||
|
(update :discount parsedouble)
|
||||||
|
(update :total parsedouble)))
|
||||||
|
lis)))
|
||||||
|
(update :charges (fn [charges]
|
||||||
|
(map
|
||||||
|
(fn [charge]
|
||||||
|
(-> charge
|
||||||
|
(update :tip parsedouble)
|
||||||
|
(update :total parsedouble)))
|
||||||
|
charges)))))
|
||||||
|
(:all-sales-orders (:data payments))))})))
|
||||||
|
|
||||||
(GET "/accounts/export" {:keys [query-params identity]}
|
(defn export-expected-deposits [{:keys [query-params identity]}]
|
||||||
(assert-admin identity)
|
(assert-admin identity)
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
"export:accounts"}}]
|
"export:deposit"}}]
|
||||||
(let [client-id (d-clients/code->id (query-params "client-code"))
|
(let [query [[:all_expected_deposits
|
||||||
query [[:all-accounts
|
(cond-> {:client-code (query-params "client-code")}
|
||||||
[:id :numeric_code :type :applicability :location :name [:client_overrides [:name [:client [:id :code :name]]]]]]]
|
(query-params "after") (assoc :date-range {:start (query-params "after")
|
||||||
all-accounts (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
|
:end nil}))
|
||||||
|
[:id
|
||||||
|
[:client [:id :name :code]]
|
||||||
|
:location
|
||||||
|
:external_id
|
||||||
|
:total
|
||||||
|
:fee
|
||||||
|
:date]]]
|
||||||
|
payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
|
||||||
|
{:body
|
||||||
|
(seq (map
|
||||||
|
(fn [d]
|
||||||
|
(-> d
|
||||||
|
(update :fee #(some-> % Double/parseDouble))
|
||||||
|
(update :total #(some-> % Double/parseDouble))))
|
||||||
|
(:all-expected-deposits (:data payments))))})))
|
||||||
|
|
||||||
(list (transduce
|
|
||||||
(comp
|
|
||||||
(filter (fn [a]
|
|
||||||
(let [overriden-clients (set (map (comp :id :client) (:client-overrides a)))]
|
|
||||||
(or (nil? (:applicability a))
|
|
||||||
(= :global (:applicability a))
|
|
||||||
(overriden-clients (str client-id))))))
|
|
||||||
(map (fn [a]
|
|
||||||
(let [client->name (reduce
|
|
||||||
(fn [override co]
|
|
||||||
(assoc override (str (:id (:client co))) (:name co)))
|
|
||||||
{}
|
|
||||||
(:client-overrides a))]
|
|
||||||
(-> a
|
|
||||||
(assoc :global-name (:name a))
|
|
||||||
(assoc :client-name (client->name (str client-id) (:name a)))
|
|
||||||
(dissoc :client-overrides))))))
|
|
||||||
conj
|
|
||||||
(list)
|
|
||||||
(:all-accounts (:data all-accounts)))))))
|
|
||||||
|
|
||||||
(GET "/transactions/export" {:keys [query-params identity]}
|
(generate/add-encoder org.joda.time.DateTime
|
||||||
(assert-admin identity)
|
(fn [c jsonGenerator]
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
(.writeString jsonGenerator (str c))))
|
||||||
"export:transactions"}}]
|
|
||||||
(let [[transactions] (d-transactions/get-graphql {:client-code (query-params "client-code")
|
|
||||||
#_#_:original-id (Integer/parseInt (query-params "original"))
|
|
||||||
:count Integer/MAX_VALUE})]
|
|
||||||
|
|
||||||
|
|
||||||
(map (comp ->graphql (fn [i]
|
|
||||||
(cond-> i
|
|
||||||
true (update :transaction/date to-date)
|
|
||||||
true (update :transaction/post-date to-date)
|
|
||||||
(:transaction/payment i) (update-in [:transaction/payment :payment/date] to-date)
|
|
||||||
(:transaction/expected-deposit i) (update-in [:transaction/expected-deposit :expected-deposit/date] to-date))))
|
|
||||||
transactions)))
|
|
||||||
)
|
|
||||||
|
|
||||||
(GET "/transactions/export2" {:keys [query-params identity]}
|
|
||||||
(assert-admin identity)
|
|
||||||
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
|
||||||
"export:transactions2"}}]
|
|
||||||
(let [db (d/db conn)]
|
|
||||||
(->>
|
|
||||||
(d/query {:query {:find ['?e]
|
|
||||||
:in ['$ '?client-code]
|
|
||||||
:where ['[?e :transaction/client ?client-code]]}
|
|
||||||
:args [db [:client/code (query-params "client-code")]]})
|
|
||||||
(map first)
|
|
||||||
(map (fn [e]
|
|
||||||
(let [e (d/entity db e)
|
|
||||||
client (:transaction/client e)
|
|
||||||
bank-account (:transaction/bank-account e)]
|
|
||||||
{:id (:db/id e)
|
|
||||||
:date (:transaction/date e)
|
|
||||||
:post_date (:transaction/post-date e)
|
|
||||||
:client { :code (:client/code client)
|
|
||||||
:id (:db/id client)
|
|
||||||
:name (:client/name client)}
|
|
||||||
:amount (:transaction/amount e)
|
|
||||||
:description_original (:transaction/description-original e)
|
|
||||||
:approval_status (:transaction/approval-status e)
|
|
||||||
:bank_account {:name (:bank-account/name bank-account)
|
|
||||||
:code (:bank-account/code bank-account)
|
|
||||||
:id (:db/id bank-account)}})))))))
|
|
||||||
|
|
||||||
(GET "/raw" {:keys [query-params identity]}
|
(defn export-clients[{:keys [identity]}]
|
||||||
(assert-admin identity)
|
(assert-admin identity)
|
||||||
(log/info "Executing raw query " (get query-params "query" ))
|
{:body (into []
|
||||||
(statsd/time! [(str "export.time") {:tags #{"export:raw"}}]
|
(map <-graphql)
|
||||||
(into (list) (apply d/q (read-string (get query-params "query" )) (into [(d/db conn)] (read-string (get query-params "args" "[]")))))))))
|
(d-clients/get-all))})
|
||||||
|
|
||||||
(defroutes export-routes
|
(defn export-vendors [{:keys [identity]}]
|
||||||
(routes
|
(assert-admin identity)
|
||||||
(wrap-routes api-key-authed-routes
|
(statsd/time! [(str "export.time") {:tags #{"export:vendors"}}]
|
||||||
wrap-csv-response)
|
{:body
|
||||||
|
(map <-graphql (->> (d/q '[:find [?e ...]
|
||||||
|
:in $
|
||||||
|
:where [?e :vendor/name]]
|
||||||
|
(d/db conn))
|
||||||
|
(d/pull-many (d/db conn) vendor/default-read)))}))
|
||||||
|
|
||||||
(wrap-routes (wrap-routes admin-only-routes wrap-secure)
|
(defn export-company-vendors [{:keys [identity query-params]}]
|
||||||
wrap-json-response)))
|
(statsd/time! [(str "export.time") {:tags #{"export:company-vendors"}}]
|
||||||
|
(let [client (:db/id (d/pull (d/db conn) [:db/id] [:client/code (get query-params "client")]))
|
||||||
|
|
||||||
|
_ (assert-can-see-client identity client)
|
||||||
|
data (->> (d/q '[:find (pull ?v [:vendor/name
|
||||||
|
:vendor/terms
|
||||||
|
{:vendor/default-account [:account/name :account/numeric-code
|
||||||
|
{:account/client-overrides
|
||||||
|
[:account-client-override/client
|
||||||
|
:account-client-override/name]}]
|
||||||
|
:vendor/terms-overrides [:vendor-terms-override/client
|
||||||
|
:vendor-terms-override/terms]
|
||||||
|
:vendor/account-overrides [:vendor-account-override/client
|
||||||
|
{:vendor-account-override/account [:account/numeric-code :account/name
|
||||||
|
{:account/client-overrides
|
||||||
|
[:account-client-override/client
|
||||||
|
:account-client-override/name]}]}]
|
||||||
|
:vendor/address [:address/street1 :address/city :address/state :address/zip]}])
|
||||||
|
:in $ ?c
|
||||||
|
:where [?vu :vendor-usage/client ?c]
|
||||||
|
[?vu :vendor-usage/count ?count]
|
||||||
|
[(>= ?vu 0)]
|
||||||
|
[?vu :vendor-usage/vendor ?v]
|
||||||
|
(not [?v :vendor/hidden true])]
|
||||||
|
(d/db conn)
|
||||||
|
client)
|
||||||
|
(map (fn [[v]]
|
||||||
|
[(-> v :vendor/name)
|
||||||
|
(-> v :vendor/address :address/street1)
|
||||||
|
(-> v :vendor/address :address/city)
|
||||||
|
(-> v :vendor/address :address/state)
|
||||||
|
(-> v :vendor/address :address/zip)
|
||||||
|
(-> v (vendor/terms-for-client-id client) )
|
||||||
|
(-> v (vendor/account-for-client-id client) (accounts/clientize client) :account/name)
|
||||||
|
(-> v (vendor/account-for-client-id client) :account/numeric-code)
|
||||||
|
]
|
||||||
|
))
|
||||||
|
(into [["Vendor Name" "Address" "City" "State" "Zip" "Terms" "Account" "Account Code"]]))]
|
||||||
|
{:body
|
||||||
|
(into (list)
|
||||||
|
data)})))
|
||||||
|
|
||||||
|
(defn export-ledger [{:keys [identity query-params]}]
|
||||||
|
(let [start-date (or (some-> (query-params "start-date")
|
||||||
|
(atime/parse atime/iso-date))
|
||||||
|
(time/plus (time/now) (time/days -120)))]
|
||||||
|
(log/info "exporting for " (query-params "client-code") "starting" start-date)
|
||||||
|
(assert-admin identity)
|
||||||
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
|
"export:ledger2"}}]
|
||||||
|
(let [results (->> (d/q '[:find (pull ?e [:db/id
|
||||||
|
:journal-entry/external-id
|
||||||
|
:journal-entry/cleared
|
||||||
|
:journal-entry/alternate-description
|
||||||
|
:journal-entry/date
|
||||||
|
:journal-entry/note
|
||||||
|
:journal-entry/amount
|
||||||
|
:journal-entry/source
|
||||||
|
:journal-entry/cleared-against
|
||||||
|
:journal-entry/original-entity
|
||||||
|
{:journal-entry/client [:client/name :client/code :db/id]
|
||||||
|
:journal-entry/vendor [:vendor/name :db/id]
|
||||||
|
:journal-entry/line-items
|
||||||
|
[:db/id
|
||||||
|
:journal-entry-line/location
|
||||||
|
:journal-entry-line/debit
|
||||||
|
:journal-entry-line/credit
|
||||||
|
{:journal-entry-line/account
|
||||||
|
[:bank-account/include-in-reports
|
||||||
|
:bank-account/bank-name
|
||||||
|
:bank-account/numeric-code
|
||||||
|
:bank-account/code
|
||||||
|
:bank-account/visible
|
||||||
|
:bank-account/name
|
||||||
|
:bank-account/number
|
||||||
|
:account/code
|
||||||
|
:account/name
|
||||||
|
:account/numeric-code
|
||||||
|
:account/location
|
||||||
|
{:account/type [:db/ident :db/id]}
|
||||||
|
{:bank-account/type [:db/ident :db/id]}]}]}])
|
||||||
|
:in $ ?c ?start-date
|
||||||
|
:where [?e :journal-entry/client ?c]
|
||||||
|
[?e :journal-entry/date ?date]
|
||||||
|
[(>= ?date ?start-date)]]
|
||||||
|
(d/db conn)
|
||||||
|
[:client/code (query-params "client-code")]
|
||||||
|
(coerce/to-date start-date)))
|
||||||
|
tf-result (transduce (comp
|
||||||
|
(map first)
|
||||||
|
(filter (fn [je]
|
||||||
|
(every?
|
||||||
|
(fn [jel]
|
||||||
|
(let [include-in-reports (-> jel :journal-entry-line/account :bank-account/include-in-reports)]
|
||||||
|
(or (nil? include-in-reports)
|
||||||
|
(true? include-in-reports))))
|
||||||
|
(:journal-entry/line-items je))))
|
||||||
|
(map <-graphql))
|
||||||
|
conj
|
||||||
|
(list)
|
||||||
|
results)]
|
||||||
|
{:body
|
||||||
|
tf-result}))))
|
||||||
|
|
||||||
|
(defn export-accounts [{:keys [query-params identity]}]
|
||||||
|
(assert-admin identity)
|
||||||
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
|
"export:accounts"}}]
|
||||||
|
(let [client-id (d-clients/code->id (query-params "client-code"))
|
||||||
|
query [[:all-accounts
|
||||||
|
[:id :numeric_code :type :applicability :location :name [:client_overrides [:name [:client [:id :code :name]]]]]]]
|
||||||
|
all-accounts (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
|
||||||
|
|
||||||
|
{:body
|
||||||
|
(list (transduce
|
||||||
|
(comp
|
||||||
|
(filter (fn [a]
|
||||||
|
(let [overriden-clients (set (map (comp :id :client) (:client-overrides a)))]
|
||||||
|
(or (nil? (:applicability a))
|
||||||
|
(= :global (:applicability a))
|
||||||
|
(overriden-clients (str client-id))))))
|
||||||
|
(map (fn [a]
|
||||||
|
(let [client->name (reduce
|
||||||
|
(fn [override co]
|
||||||
|
(assoc override (str (:id (:client co))) (:name co)))
|
||||||
|
{}
|
||||||
|
(:client-overrides a))]
|
||||||
|
(-> a
|
||||||
|
(assoc :global-name (:name a))
|
||||||
|
(assoc :client-name (client->name (str client-id) (:name a)))
|
||||||
|
(dissoc :client-overrides))))))
|
||||||
|
conj
|
||||||
|
(list)
|
||||||
|
(:all-accounts (:data all-accounts))))})))
|
||||||
|
|
||||||
|
(defn export-transactions2 [{:keys [query-params identity]}]
|
||||||
|
(assert-admin identity)
|
||||||
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
|
"export:transactions2"}}]
|
||||||
|
{:body (let [db (d/db conn)]
|
||||||
|
(->>
|
||||||
|
(d/query {:query {:find ['?e]
|
||||||
|
:in ['$ '?client-code]
|
||||||
|
:where ['[?e :transaction/client ?client-code]]}
|
||||||
|
:args [db [:client/code (query-params "client-code")]]})
|
||||||
|
(map first)
|
||||||
|
(map (fn [e]
|
||||||
|
(let [e (d/entity db e)
|
||||||
|
client (:transaction/client e)
|
||||||
|
bank-account (:transaction/bank-account e)]
|
||||||
|
{:id (:db/id e)
|
||||||
|
:date (:transaction/date e)
|
||||||
|
:post_date (:transaction/post-date e)
|
||||||
|
:client { :code (:client/code client)
|
||||||
|
:id (:db/id client)
|
||||||
|
:name (:client/name client)}
|
||||||
|
:amount (:transaction/amount e)
|
||||||
|
:description_original (:transaction/description-original e)
|
||||||
|
:approval_status (:transaction/approval-status e)
|
||||||
|
:bank_account {:name (:bank-account/name bank-account)
|
||||||
|
:code (:bank-account/code bank-account)
|
||||||
|
:id (:db/id bank-account)}})))))}))
|
||||||
|
|
||||||
|
(defn export-transactions [{:keys [query-params identity]}]
|
||||||
|
(assert-admin identity)
|
||||||
|
(statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
|
||||||
|
"export:transactions"}}]
|
||||||
|
(let [[transactions] (d-transactions/get-graphql {:client-code (query-params "client-code")
|
||||||
|
#_#_:original-id (Integer/parseInt (query-params "original"))
|
||||||
|
:count Integer/MAX_VALUE})]
|
||||||
|
|
||||||
|
|
||||||
|
{:body (map
|
||||||
|
(comp ->graphql
|
||||||
|
(fn [i]
|
||||||
|
(cond-> i
|
||||||
|
true (update :transaction/date to-date)
|
||||||
|
true (update :transaction/post-date to-date)
|
||||||
|
(:transaction/payment i) (update-in [:transaction/payment :payment/date] to-date)
|
||||||
|
(:transaction/expected-deposit i) (update-in [:transaction/expected-deposit :expected-deposit/date] to-date))))
|
||||||
|
transactions)})))
|
||||||
|
|
||||||
|
(defn export-raw [{:keys [query-params identity]}]
|
||||||
|
(assert-admin identity)
|
||||||
|
(log/info "Executing raw query " (get query-params "query" ))
|
||||||
|
(statsd/time! [(str "export.time") {:tags #{"export:raw"}}]
|
||||||
|
{:body
|
||||||
|
(into (list) (apply d/q (read-string (get query-params "query" )) (into [(d/db conn)] (read-string (get query-params "args" "[]")))))}))
|
||||||
|
|
||||||
|
|
||||||
|
(def routes2 {"api/" {"sales/" {"aggregated/" {#"export/?" {:get :aggregated-sales-export}}
|
||||||
|
#"export/?" {:get :export-sales}}
|
||||||
|
"invoices/" {#"export/?" {:get :export-invoices}}
|
||||||
|
"payments/" {#"export/?" {:get :export-payments}}
|
||||||
|
"expected-deposit/" {#"export/?" {:get :export-expected-deposits}}
|
||||||
|
"clients/" {#"export/?" {:get :export-clients}}
|
||||||
|
"vendors/" {#"export/?" {:get :export-vendors}
|
||||||
|
"/company" {#"export/?" {:get :export-company-vendors}}}
|
||||||
|
"ledger/" {#"export/?" {:get :export-ledger}}
|
||||||
|
"accounts/" {#"export/?" {:get :export-accounts}}
|
||||||
|
"transactions/" {#"export/?" {:get :export-transactions}
|
||||||
|
#"export2/?" {:get :export-transactions2}}
|
||||||
|
#"raw/?" {:get :export-raw}}})
|
||||||
|
|
||||||
|
(def match->handler {:aggregated-sales-export (wrap-csv-response aggregated-sales-export)
|
||||||
|
:export-invoices (-> export-invoices wrap-json-response wrap-secure )
|
||||||
|
:export-payments (-> export-payments wrap-json-response wrap-secure)
|
||||||
|
:export-sales (-> export-sales wrap-json-response wrap-secure)
|
||||||
|
:export-expected-deposits (-> export-expected-deposits wrap-json-response wrap-secure)
|
||||||
|
:export-clients (-> export-clients wrap-json-response wrap-secure)
|
||||||
|
:export-vendors (-> export-vendors wrap-json-response wrap-secure)
|
||||||
|
:export-company-vendors (-> export-company-vendors wrap-csv-response wrap-secure)
|
||||||
|
:export-ledger (-> export-ledger wrap-json-response wrap-secure)
|
||||||
|
:export-accounts (-> export-accounts wrap-json-response wrap-secure)
|
||||||
|
:export-transactions (-> export-transactions wrap-json-response wrap-secure)
|
||||||
|
:export-transactions2 (-> export-transactions2 wrap-json-response wrap-secure)
|
||||||
|
:export-raw (-> export-raw wrap-json-response wrap-secure)})
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
(ns auto-ap.routes.ezcater
|
(ns auto-ap.routes.ezcater
|
||||||
(:require
|
(:require
|
||||||
[clojure.tools.logging :as log]
|
|
||||||
[compojure.core :refer [context defroutes GET POST wrap-routes]]
|
|
||||||
[ring.middleware.json :refer [wrap-json-params]]
|
|
||||||
[auto-ap.ezcater.core :as e]
|
[auto-ap.ezcater.core :as e]
|
||||||
[ring.util.request :refer [body-string]]))
|
[auto-ap.logging :as alog]
|
||||||
|
[ring.middleware.json :refer [wrap-json-params]]))
|
||||||
|
|
||||||
(defroutes routes
|
(defn handle-ezcater [{:keys [request-method json-params] :as r}]
|
||||||
(wrap-routes
|
(cond
|
||||||
(context "/ezcater" []
|
(= :get request-method)
|
||||||
(GET "/event" request
|
{:status 200
|
||||||
(log/info (str "GET EVENT " (body-string request) request))
|
:headers {"Content-Type" "application/json"}
|
||||||
{:status 200
|
:body "{}"}
|
||||||
:headers {"Content-Type" "application/json"}
|
|
||||||
:body "{}"})
|
(= :post request-method)
|
||||||
(POST "/event" request
|
(do
|
||||||
(log/info (str "POST EVENT " (body-string request) request))
|
(alog/info ::ezcater-request
|
||||||
(e/import-order (:json-params request))
|
:json-params json-params)
|
||||||
{:status 200
|
(e/import-order json-params)
|
||||||
:headers {"Content-Type" "application/json"}
|
{:status 200
|
||||||
:body "{}"}))
|
:headers {"Content-Type" "application/json"}
|
||||||
wrap-json-params))
|
:body "{}"})
|
||||||
|
|
||||||
|
:else
|
||||||
|
{:status 404}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(def routes {"api/" {"ezcater/" {#"event/?" :ezcater-event}}})
|
||||||
|
(def match->handler {:ezcater-event (-> handle-ezcater
|
||||||
|
wrap-json-params)})
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
[auto-ap.logging :refer [warn-event]]
|
[auto-ap.logging :refer [warn-event]]
|
||||||
[buddy.auth :refer [throw-unauthorized]]
|
[buddy.auth :refer [throw-unauthorized]]
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
[compojure.core :refer [GET POST context defroutes
|
|
||||||
wrap-routes]]
|
|
||||||
[clojure.tools.logging :as log]))
|
[clojure.tools.logging :as log]))
|
||||||
(defn handle-graphql [{:keys [request-method query-params] :as r}]
|
(defn handle-graphql [{:keys [request-method query-params] :as r}]
|
||||||
(when (= "none" (:user/role (:identity r)))
|
(when (= "none" (:user/role (:identity r)))
|
||||||
@@ -39,9 +37,5 @@
|
|||||||
:headers {"Content-Type" "application/edn"}}))))))
|
:headers {"Content-Type" "application/edn"}}))))))
|
||||||
|
|
||||||
|
|
||||||
(defroutes routes
|
(def routes {"api/" {#"graphql/?" :graphql}})
|
||||||
(wrap-routes
|
(def match->handler {:graphql (wrap-secure handle-graphql)})
|
||||||
(context "/graphql" []
|
|
||||||
(GET "/" x (handle-graphql x))
|
|
||||||
(POST "/" x (handle-graphql x)))
|
|
||||||
wrap-secure))
|
|
||||||
|
|||||||
19
src/clj/auto_ap/routes/health.clj
Normal file
19
src/clj/auto_ap/routes/health.clj
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
(ns auto-ap.routes.health
|
||||||
|
(:require [mount.core :as mount]))
|
||||||
|
|
||||||
|
(def running? (atom false))
|
||||||
|
|
||||||
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
|
(mount/defstate manage-running?
|
||||||
|
:start (reset! running? true)
|
||||||
|
:stop (reset! running? false))
|
||||||
|
|
||||||
|
(defn health-check [request]
|
||||||
|
(if @running?
|
||||||
|
{:status 200
|
||||||
|
:body "Ok"}
|
||||||
|
{:status 503
|
||||||
|
:body "Application shut down"}))
|
||||||
|
|
||||||
|
(def routes {"api/" {"health-check" :health}})
|
||||||
|
(def match->handler {:health health-check})
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[compojure.core :refer [context defroutes POST wrap-routes]]
|
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.api :as d]
|
[datomic.api :as d]
|
||||||
[digest]
|
[digest]
|
||||||
@@ -385,142 +384,147 @@
|
|||||||
rows)]
|
rows)]
|
||||||
(transact-with-ledger txes nil)))
|
(transact-with-ledger txes nil)))
|
||||||
|
|
||||||
(defroutes routes
|
(defn batch-upload-transactions [{{:keys [data]} :edn-params user :identity}]
|
||||||
(wrap-routes
|
(assert-admin user)
|
||||||
(context "/" []
|
(try
|
||||||
(context "/transactions" []
|
(let [stats (manual/import-batch (manual/tabulate-data data) (:user/name user))]
|
||||||
(POST "/batch-upload"
|
{:status 200
|
||||||
{{:keys [data]} :edn-params user :identity}
|
:body (pr-str stats)
|
||||||
(assert-admin user)
|
:headers {"Content-Type" "application/edn"}})
|
||||||
(try
|
(catch Exception e
|
||||||
(let [stats (manual/import-batch (manual/tabulate-data data) (:user/name user))]
|
(log/error e)
|
||||||
{:status 200
|
{:status 500
|
||||||
:body (pr-str stats)
|
:body (pr-str {:message (.getMessage e)
|
||||||
:headers {"Content-Type" "application/edn"}})
|
:error (.toString e)
|
||||||
(catch Exception e
|
:data (ex-data e)})
|
||||||
(log/error e)
|
:headers {"Content-Type" "application/edn"}})))
|
||||||
{:status 500
|
|
||||||
:body (pr-str {:message (.getMessage e)
|
|
||||||
:error (.toString e)
|
|
||||||
:data (ex-data e)})
|
|
||||||
:headers {"Content-Type" "application/edn"}}))))
|
|
||||||
|
|
||||||
(context "/invoices" []
|
(defn upload-invoices [{{files :file
|
||||||
(POST "/upload"
|
files-2 "file"
|
||||||
{{files :file
|
client :client
|
||||||
files-2 "file"
|
client-2 "client"
|
||||||
client :client
|
location :location
|
||||||
client-2 "client"
|
location-2 "location"
|
||||||
location :location
|
vendor :vendor
|
||||||
location-2 "location"
|
vendor-2 "vendor"} :params
|
||||||
vendor :vendor
|
user :identity}]
|
||||||
vendor-2 "vendor"} :params
|
(let [files (or files files-2)
|
||||||
user :identity}
|
client (or client client-2)
|
||||||
(let [files (or files files-2)
|
location (or location location-2)
|
||||||
client (or client client-2)
|
vendor (some-> (or vendor vendor-2)
|
||||||
location (or location location-2)
|
(Long/parseLong))
|
||||||
vendor (some-> (or vendor vendor-2)
|
{:keys [filename tempfile]} files]
|
||||||
(Long/parseLong))
|
(lc/with-context {:parsing-file filename}
|
||||||
{:keys [filename tempfile]} files]
|
(log/info tempfile)
|
||||||
(lc/with-context {:parsing-file filename}
|
(try
|
||||||
(log/info tempfile)
|
(let [extension (last (str/split (.getName (io/file filename)) #"\."))
|
||||||
(try
|
s3-location (str "invoice-files/" (str (UUID/randomUUID)) "." extension)
|
||||||
(let [extension (last (str/split (.getName (io/file filename)) #"\."))
|
_ (s3/put-object (:data-bucket env)
|
||||||
s3-location (str "invoice-files/" (str (UUID/randomUUID)) "." extension)
|
s3-location
|
||||||
_ (s3/put-object (:data-bucket env)
|
(io/input-stream tempfile)
|
||||||
s3-location
|
{:content-type (if (= "csv" extension)
|
||||||
(io/input-stream tempfile)
|
"text/csv"
|
||||||
{:content-type (if (= "csv" extension)
|
"application/pdf")
|
||||||
"text/csv"
|
:content-length (.length tempfile)})
|
||||||
"application/pdf")
|
imports (->> (parse/parse-file (.getPath tempfile) filename)
|
||||||
:content-length (.length tempfile)})
|
(map #(assoc %
|
||||||
imports (->> (parse/parse-file (.getPath tempfile) filename)
|
:client-override client
|
||||||
(map #(assoc %
|
:location-override location
|
||||||
:client-override client
|
:vendor-override vendor
|
||||||
:location-override location
|
:source-url (str "http://" (:data-bucket env)
|
||||||
:vendor-override vendor
|
".s3-website-us-east-1.amazonaws.com/"
|
||||||
:source-url (str "http://" (:data-bucket env)
|
s3-location))))]
|
||||||
".s3-website-us-east-1.amazonaws.com/"
|
(import-uploaded-invoice user imports))
|
||||||
s3-location))))]
|
{:status 200
|
||||||
(import-uploaded-invoice user imports))
|
:body (pr-str {})
|
||||||
{:status 200
|
:headers {"Content-Type" "application/edn"}}
|
||||||
:body (pr-str {})
|
(catch Exception e
|
||||||
:headers {"Content-Type" "application/edn"}}
|
(log/warn e)
|
||||||
(catch Exception e
|
{:status 400
|
||||||
(log/warn e)
|
:body (pr-str {:message (.getMessage e)
|
||||||
{:status 400
|
:error (.toString e)
|
||||||
:body (pr-str {:message (.getMessage e)
|
:data (ex-data e)})
|
||||||
:error (.toString e)
|
:headers {"Content-Type" "application/edn"}})))))
|
||||||
:data (ex-data e)})
|
|
||||||
:headers {"Content-Type" "application/edn"}})))))
|
|
||||||
|
|
||||||
(POST "/upload-integreat"
|
(defn bulk-upload-invoices [{{:keys [excel-rows]} :edn-params user :identity}]
|
||||||
{{:keys [excel-rows]} :edn-params user :identity}
|
(assert-admin user)
|
||||||
(assert-admin user)
|
(let [parsed-invoice-rows (parse-invoice-rows excel-rows)
|
||||||
(let [parsed-invoice-rows (parse-invoice-rows excel-rows)
|
existing-rows (set (d-invoices/get-existing-set))
|
||||||
existing-rows (set (d-invoices/get-existing-set))
|
grouped-rows (group-by
|
||||||
grouped-rows (group-by
|
(fn [i]
|
||||||
(fn [i]
|
(cond (seq (:errors i))
|
||||||
(cond (seq (:errors i))
|
:error
|
||||||
:error
|
|
||||||
|
|
||||||
(existing-rows [(:vendor-id i) (:client-id i) (:invoice-number i)])
|
(existing-rows [(:vendor-id i) (:client-id i) (:invoice-number i)])
|
||||||
:exists
|
:exists
|
||||||
|
|
||||||
:else
|
:else
|
||||||
:new))
|
:new))
|
||||||
parsed-invoice-rows)
|
parsed-invoice-rows)
|
||||||
vendors-not-found (->> parsed-invoice-rows
|
vendors-not-found (->> parsed-invoice-rows
|
||||||
(filter #(and (nil? (:vendor-id %))
|
(filter #(and (nil? (:vendor-id %))
|
||||||
(not= "Cash" (:check %))))
|
(not= "Cash" (:check %))))
|
||||||
(map :vendor-name)
|
(map :vendor-name)
|
||||||
set)
|
set)
|
||||||
_ (transact-with-ledger (invoice-rows->transaction (:new grouped-rows)
|
_ (transact-with-ledger (invoice-rows->transaction (:new grouped-rows)
|
||||||
user)
|
user)
|
||||||
user)]
|
user)]
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (pr-str {:imported (count (:new grouped-rows))
|
:body (pr-str {:imported (count (:new grouped-rows))
|
||||||
:already-imported (count (:exists grouped-rows))
|
:already-imported (count (:exists grouped-rows))
|
||||||
:vendors-not-found vendors-not-found
|
:vendors-not-found vendors-not-found
|
||||||
:errors (map #(dissoc % :date) (:error grouped-rows))})
|
:errors (map #(dissoc % :date) (:error grouped-rows))})
|
||||||
:headers {"Content-Type" "application/edn"}})))
|
:headers {"Content-Type" "application/edn"}}))
|
||||||
(POST "/transactions/cleared-against"
|
|
||||||
{{files :file
|
(defn cleared-against [{{files :file
|
||||||
files-2 "file"} :params
|
files-2 "file"} :params
|
||||||
user :identity}
|
user :identity}]
|
||||||
(let [files (or files files-2)
|
(let [files (or files files-2)
|
||||||
{:keys [tempfile]} files]
|
{:keys [tempfile]} files]
|
||||||
(assert-admin user)
|
(assert-admin user)
|
||||||
(try
|
(try
|
||||||
(import-transactions-cleared-against (.getPath tempfile))
|
(import-transactions-cleared-against (.getPath tempfile))
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (pr-str {})
|
:body (pr-str {})
|
||||||
:headers {"Content-Type" "application/edn"}}
|
:headers {"Content-Type" "application/edn"}}
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(log/error e)
|
(log/error e)
|
||||||
{:status 500
|
{:status 500
|
||||||
:body (pr-str {:message (.getMessage e)
|
:body (pr-str {:message (.getMessage e)
|
||||||
:error (.toString e)
|
:error (.toString e)
|
||||||
:data (ex-data e)})
|
:data (ex-data e)})
|
||||||
:headers {"Content-Type" "application/edn"}}))))
|
:headers {"Content-Type" "application/edn"}}))))
|
||||||
(wrap-json-response (POST "/account-overrides"
|
|
||||||
{{files :file
|
|
||||||
files-2 "file"
|
|
||||||
client :client
|
(defn bulk-account-overrides [{{files :file
|
||||||
client-2 "client"} :params
|
files-2 "file"
|
||||||
user :identity}
|
client :client
|
||||||
(let [files (or files files-2)
|
client-2 "client"} :params
|
||||||
client (or client client-2)
|
user :identity}]
|
||||||
{:keys [tempfile]} files]
|
(let [files (or files files-2)
|
||||||
(assert-admin user)
|
client (or client client-2)
|
||||||
(try
|
{:keys [tempfile]} files]
|
||||||
{:status 200
|
(assert-admin user)
|
||||||
:body (import-account-overrides client (.getPath tempfile))
|
(try
|
||||||
:headers {"Content-Type" "application/json"}}
|
{:status 200
|
||||||
(catch Exception e
|
:body (import-account-overrides client (.getPath tempfile))
|
||||||
(log/error e)
|
:headers {"Content-Type" "application/json"}}
|
||||||
{:status 500
|
(catch Exception e
|
||||||
:body {:message (.getMessage e)
|
(log/error e)
|
||||||
:data (ex-data e)}
|
{:status 500
|
||||||
:headers {"Content-Type" "application/json"}}))))))
|
:body {:message (.getMessage e)
|
||||||
wrap-secure))
|
:data (ex-data e)}
|
||||||
|
:headers {"Content-Type" "application/json"}}))))
|
||||||
|
|
||||||
|
(def routes {"api/" {"transactions/" {:post {#"batch-upload/?" :batch-upload-transactions
|
||||||
|
#"cleared-against/?" :cleared-against}}
|
||||||
|
"invoices/" {:post {#"upload/?" :upload-invoices
|
||||||
|
#"upload-integreat/?" :bulk-upload-invoices}}
|
||||||
|
:post {#"account-overrides/?" :bulk-account-overrides}}})
|
||||||
|
|
||||||
|
(def match->handler {:batch-upload-transactions (wrap-secure batch-upload-transactions)
|
||||||
|
:upload-invoices (wrap-secure upload-invoices)
|
||||||
|
:bulk-upload-invoices (wrap-secure bulk-upload-invoices)
|
||||||
|
:cleared-against (wrap-secure cleared-against)
|
||||||
|
:bulk-account-overrides (wrap-secure (wrap-json-response bulk-account-overrides))})
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
(ns auto-ap.routes.queries
|
(ns auto-ap.routes.queries
|
||||||
(:require [amazonica.aws.s3 :as s3]
|
(:require
|
||||||
[auto-ap.datomic :refer [conn]]
|
[amazonica.aws.s3 :as s3]
|
||||||
[auto-ap.graphql.utils :refer [assert-admin]]
|
[auto-ap.datomic :refer [conn]]
|
||||||
[clojure.data.csv :as csv]
|
[auto-ap.graphql.utils :refer [assert-admin]]
|
||||||
[clojure.java.io :as io]
|
[clojure.data.csv :as csv]
|
||||||
[clojure.string :as str]
|
[clojure.edn :as edn]
|
||||||
[clojure.tools.logging :as log]
|
[clojure.java.io :as io]
|
||||||
[com.unbounce.dogstatsd.core :as statsd]
|
[clojure.string :as str]
|
||||||
[compojure.core
|
[clojure.tools.logging :as log]
|
||||||
:refer
|
[com.unbounce.dogstatsd.core :as statsd]
|
||||||
[context defroutes GET POST PUT routes wrap-routes]]
|
[config.core :refer [env]]
|
||||||
[config.core :refer [env]]
|
[datomic.api :as d]
|
||||||
[clojure.edn :as edn]
|
[ring.middleware.json :refer [wrap-json-response]]
|
||||||
[datomic.api :as d]
|
[ring.util.request :refer [body-string]]
|
||||||
[ring.middleware.json :refer [wrap-json-response]]
|
[unilog.context :as lc])
|
||||||
[ring.util.request :refer [body-string]]
|
(:import
|
||||||
[unilog.context :as lc])
|
(java.util UUID)))
|
||||||
(:import java.util.UUID))
|
|
||||||
|
|
||||||
(defn wrap-csv-response [handler]
|
(defn wrap-csv-response [handler]
|
||||||
(fn [request]
|
(fn [request]
|
||||||
@@ -62,64 +61,77 @@
|
|||||||
:csv-results-url (str "/api/queries/" guid "/results/csv")
|
:csv-results-url (str "/api/queries/" guid "/results/csv")
|
||||||
:json-results-url (str "/api/queries/" guid "/results/json")}}))
|
:json-results-url (str "/api/queries/" guid "/results/json")}}))
|
||||||
|
|
||||||
(def json-routes
|
|
||||||
(context "/queries" []
|
|
||||||
(POST "/" {:keys [query-params identity] :as request}
|
|
||||||
(assert-admin identity)
|
|
||||||
(log/info "Note" (query-params "note"))
|
|
||||||
(put-query (str (UUID/randomUUID)) (body-string request) (query-params "note")))
|
|
||||||
(PUT "/:query-id" {:keys [query-params identity params] :as request}
|
|
||||||
(assert-admin identity)
|
|
||||||
(log/info "Note" (query-params "note"))
|
|
||||||
(put-query (:query-id params) (body-string request) (query-params "note")))
|
|
||||||
(GET "/:query-id" {:keys [identity params]}
|
|
||||||
(assert-admin identity)
|
|
||||||
(let [{:keys [query-id]} params
|
|
||||||
obj (s3/get-object :bucket-name (:data-bucket env)
|
|
||||||
:key (str "queries/" query-id))
|
|
||||||
query-string (str (slurp (:object-content obj)))]
|
|
||||||
(log/info obj)
|
|
||||||
{:body {:query query-string
|
|
||||||
:note (:note (:user-metadata (:object-metadata obj)))
|
|
||||||
:id query-id
|
|
||||||
:csv-results-url (str "/api/queries/" query-id "/results/csv")
|
|
||||||
:json-results-url (str "/api/queries/" query-id "/results/json")}}))
|
|
||||||
|
|
||||||
|
|
||||||
(GET "/" {:keys [identity]}
|
|
||||||
(assert-admin identity)
|
|
||||||
(let [obj (s3/list-objects :bucket-name (:data-bucket env)
|
|
||||||
:prefix (str "queries/"))]
|
|
||||||
(log/info obj)
|
|
||||||
{:body (->> (:object-summaries obj)
|
|
||||||
(map (fn [o]
|
|
||||||
{:last-modified (.toString (:last-modified o))
|
|
||||||
:key (str/replace (:key o) #"^queries\/" "")})))}))
|
|
||||||
|
|
||||||
(GET "/:query-id/results/json" {:keys [query-params params]}
|
|
||||||
(statsd/time! [(str "export.query.time") {:tags #{(str "query:" (:query-id params))}}]
|
|
||||||
{:body (execute-query query-params params)}))))
|
|
||||||
|
|
||||||
|
|
||||||
(def raw-routes
|
(defn get-queries [{:keys [identity]}]
|
||||||
(context "/queries" []
|
(assert-admin identity)
|
||||||
(GET "/:query-id/raw" {:keys [identity params]}
|
(let [obj (s3/list-objects :bucket-name (:data-bucket env)
|
||||||
(assert-admin identity)
|
:prefix (str "queries/"))]
|
||||||
(let [{:keys [query-id]} params
|
(log/info obj)
|
||||||
obj (s3/get-object :bucket-name (:data-bucket env)
|
{:body (->> (:object-summaries obj)
|
||||||
:key (str "queries/" query-id))
|
(map (fn [o]
|
||||||
query-string (str (slurp (:object-content obj)))]
|
{:last-modified (.toString (:last-modified o))
|
||||||
(log/info obj)
|
:key (str/replace (:key o) #"^queries\/" "")})))}))
|
||||||
{:body query-string}))))
|
|
||||||
|
(defn create-query [{:keys [query-params identity] :as request}]
|
||||||
|
(assert-admin identity)
|
||||||
|
(log/info "Note" (query-params "note"))
|
||||||
|
(put-query (str (UUID/randomUUID)) (body-string request) (query-params "note")))
|
||||||
|
|
||||||
|
(defn get-query [{:keys [identity params]} ]
|
||||||
|
(assert-admin identity)
|
||||||
|
(let [{:keys [query-id]} params
|
||||||
|
obj (s3/get-object :bucket-name (:data-bucket env)
|
||||||
|
:key (str "queries/" query-id))
|
||||||
|
query-string (str (slurp (:object-content obj)))]
|
||||||
|
(log/info obj)
|
||||||
|
{:body {:query query-string
|
||||||
|
:note (:note (:user-metadata (:object-metadata obj)))
|
||||||
|
:id query-id
|
||||||
|
:csv-results-url (str "/api/queries/" query-id "/results/csv")
|
||||||
|
:json-results-url (str "/api/queries/" query-id "/results/json")}}))
|
||||||
|
|
||||||
|
(defn update-query [{:keys [query-params identity params] :as request} ]
|
||||||
|
(assert-admin identity)
|
||||||
|
(log/info "Note" (query-params "note"))
|
||||||
|
(put-query (:query-id params) (body-string request) (query-params "note")))
|
||||||
|
|
||||||
|
(defn results-json-query [{:keys [query-params params]}]
|
||||||
|
(statsd/time! [(str "export.query.time") {:tags #{(str "query:" (:query-id params))}}]
|
||||||
|
{:body (execute-query query-params params)}))
|
||||||
|
|
||||||
|
(defn raw-query [{:keys [identity params]}]
|
||||||
|
(assert-admin identity)
|
||||||
|
(let [{:keys [query-id]} params
|
||||||
|
obj (s3/get-object :bucket-name (:data-bucket env)
|
||||||
|
:key (str "queries/" query-id))
|
||||||
|
query-string (str (slurp (:object-content obj)))]
|
||||||
|
(log/info obj)
|
||||||
|
{:body query-string}))
|
||||||
|
|
||||||
|
(defn results-csv-query [{:keys [query-params params]}]
|
||||||
|
(statsd/time! [(str "export.query.time") {:tags #{(str "query:" (:query-id params))}}]
|
||||||
|
{:body (execute-query query-params params)}))
|
||||||
|
|
||||||
|
(def routes2 {"api/" {#"queries/?" {[:query-id "/raw"] {:get :raw-query}
|
||||||
|
[:query-id "/results/csv"] {:get :results-csv-query}
|
||||||
|
[:query-id "/results/json"] {:get :results-json-query}
|
||||||
|
[:query-id] {:get :get-query
|
||||||
|
:put :update-query}
|
||||||
|
:get :get-queries
|
||||||
|
:post :create-query}}})
|
||||||
|
|
||||||
|
(def match->handler {:get-queries get-queries
|
||||||
|
:create-query create-query
|
||||||
|
:raw-query raw-query
|
||||||
|
:get-query (-> get-query
|
||||||
|
wrap-json-response)
|
||||||
|
:update-query (-> update-query
|
||||||
|
wrap-json-response)
|
||||||
|
|
||||||
|
:results-json-query (-> results-json-query
|
||||||
|
wrap-json-response)
|
||||||
|
:results-csv-query (-> results-csv-query
|
||||||
|
wrap-csv-response)})
|
||||||
|
|
||||||
|
|
||||||
(def csv-routes
|
|
||||||
(context "/queries" []
|
|
||||||
(GET "/:query-id/results/csv" {:keys [query-params params]}
|
|
||||||
(statsd/time! [(str "export.query.time") {:tags #{(str "query:" (:query-id params))}}]
|
|
||||||
{:body (execute-query query-params params)}))))
|
|
||||||
(defroutes query2-routes
|
|
||||||
(routes
|
|
||||||
raw-routes
|
|
||||||
(wrap-routes json-routes
|
|
||||||
wrap-json-response)
|
|
||||||
(wrap-routes csv-routes wrap-csv-response)))
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
(ns auto-ap.routes.utils
|
(ns auto-ap.routes.utils
|
||||||
(:require [buddy.auth :refer [authenticated?]]))
|
(:require
|
||||||
|
[auto-ap.graphql.utils :refer [is-admin?]]
|
||||||
|
[buddy.auth :refer [authenticated?]]
|
||||||
|
[auto-ap.logging :as alog]))
|
||||||
|
|
||||||
(defn wrap-secure [handler]
|
(defn wrap-secure [handler]
|
||||||
(fn [request]
|
(fn [request]
|
||||||
@@ -7,3 +10,20 @@
|
|||||||
(handler request)
|
(handler request)
|
||||||
{:status 401
|
{:status 401
|
||||||
:body "not authenticated"})))
|
:body "not authenticated"})))
|
||||||
|
|
||||||
|
(defn wrap-admin [handler]
|
||||||
|
(fn [request]
|
||||||
|
(if (is-admin? (:identity request))
|
||||||
|
(handler request)
|
||||||
|
(do
|
||||||
|
(alog/warn ::unauthenticated)
|
||||||
|
{:status 302
|
||||||
|
:headers "/login"}))))
|
||||||
|
|
||||||
|
(defn wrap-client-redirect-unauthenticated [handler]
|
||||||
|
(fn [request]
|
||||||
|
(let [response (handler request)]
|
||||||
|
(println )
|
||||||
|
(if (= 401 (get response :status))
|
||||||
|
(assoc-in response [:headers "hx-redirect"] "/login/")
|
||||||
|
response))))
|
||||||
|
|||||||
@@ -6,99 +6,113 @@
|
|||||||
[auto-ap.routes.utils :refer [wrap-secure]]
|
[auto-ap.routes.utils :refer [wrap-secure]]
|
||||||
[auto-ap.yodlee.core2 :as yodlee]
|
[auto-ap.yodlee.core2 :as yodlee]
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[compojure.core :refer [context defroutes GET POST wrap-routes]]
|
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.api :as d]))
|
[datomic.api :as d]))
|
||||||
|
|
||||||
(defroutes routes
|
(defn fastlink [{:keys [query-params identity]}]
|
||||||
(wrap-routes
|
(assert-can-see-client identity (d/pull (d/db conn) [:db/id] [:client/code (get query-params "client")]))
|
||||||
(context "/yodlee2" []
|
|
||||||
(GET "/fastlink" {:keys [query-params identity]}
|
(let [token (if-let [client-id (get query-params "client-id")]
|
||||||
(assert-can-see-client identity (d/pull (d/db conn) [:db/id] [:client/code (get query-params "client")]))
|
(-> client-id
|
||||||
|
Long/parseLong
|
||||||
(let [token (if-let [client-id (get query-params "client-id")]
|
d-clients/get-by-id
|
||||||
(-> client-id
|
:client/code
|
||||||
Long/parseLong
|
(yodlee/get-access-token))
|
||||||
d-clients/get-by-id
|
(yodlee/get-access-token (get query-params "client")))]
|
||||||
:client/code
|
{:status 200
|
||||||
(yodlee/get-access-token))
|
:headers {"Content-Type" "application/edn"}
|
||||||
(yodlee/get-access-token (get query-params "client")))]
|
:body (pr-str {:token token
|
||||||
{:status 200
|
:url (:yodlee2-fastlink env)}) }))
|
||||||
:headers {"Content-Type" "application/edn"}
|
(defn refresh-provider-accounts [{:keys [identity edn-params]}]
|
||||||
:body (pr-str {:token token
|
(assert-admin identity)
|
||||||
:url (:yodlee2-fastlink env)}) }))
|
(log/info "refreshing " edn-params)
|
||||||
(POST "/provider-accounts/refresh/" {:keys [identity edn-params]}
|
(try
|
||||||
(assert-admin identity)
|
(yodlee/refresh-provider-account (-> (:client-id edn-params)
|
||||||
(log/info "refreshing " edn-params)
|
Long/parseLong
|
||||||
(try
|
d-clients/get-by-id
|
||||||
(yodlee/refresh-provider-account (-> (:client-id edn-params)
|
:client/code)
|
||||||
Long/parseLong
|
(:provider-account-id edn-params))
|
||||||
d-clients/get-by-id
|
{:status 200
|
||||||
:client/code)
|
:headers {"Content-Type" "application/edn"}
|
||||||
(:provider-account-id edn-params))
|
:body "{}" }
|
||||||
{:status 200
|
(catch Exception e
|
||||||
:headers {"Content-Type" "application/edn"}
|
(log/error e)
|
||||||
:body "{}" }
|
{:status 400
|
||||||
(catch Exception e
|
:headers {"Content-Type" "application/edn"}
|
||||||
(log/error e)
|
:body (pr-str {:message (.getMessage e)
|
||||||
{:status 400
|
:error (.toString e)})})))
|
||||||
:headers {"Content-Type" "application/edn"}
|
|
||||||
:body (pr-str {:message (.getMessage e)
|
|
||||||
:error (.toString e)})})))
|
|
||||||
|
|
||||||
(GET "/provider-accounts/:client/:id" {:keys [identity]
|
(defn get-provider-account-detail [{:keys [identity]
|
||||||
{:keys [client id]} :route-params}
|
{:keys [client id]} :route-params}]
|
||||||
(assert-admin identity)
|
(assert-admin identity)
|
||||||
(log/info "looking-up " client id)
|
(log/info "looking-up " client id)
|
||||||
(try
|
(try
|
||||||
|
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"Content-Type" "application/edn"}
|
:headers {"Content-Type" "application/edn"}
|
||||||
:body (pr-str (yodlee/get-provider-account-detail (-> client
|
:body (pr-str (yodlee/get-provider-account-detail (-> client
|
||||||
Long/parseLong
|
Long/parseLong
|
||||||
d-clients/get-by-id
|
d-clients/get-by-id
|
||||||
:client/code)
|
:client/code)
|
||||||
id))}
|
id))}
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(log/error e)
|
(log/error e)
|
||||||
{:status 400
|
{:status 400
|
||||||
:headers {"Content-Type" "application/edn"}
|
:headers {"Content-Type" "application/edn"}
|
||||||
:body (pr-str {:message (.getMessage e)
|
:body (pr-str {:message (.getMessage e)
|
||||||
:error (.toString e)})})))
|
:error (.toString e)})})))
|
||||||
(POST "/provider-accounts/delete/" {:keys [edn-params identity]}
|
(defn reauthenticate [{:keys [identity] {:keys [id]} :route-params
|
||||||
(assert-admin identity)
|
data :edn-params}]
|
||||||
(try
|
(assert-admin identity)
|
||||||
(yodlee/delete-provider-account (-> (:client-id edn-params)
|
(try
|
||||||
Long/parseLong
|
{:status 200
|
||||||
d-clients/get-by-id
|
:headers {"Content-Type" "application/edn"}
|
||||||
:client/code)
|
:body (pr-str (yodlee/reauthenticate-and-recache
|
||||||
(:provider-account-id edn-params))
|
(-> (:client-id data)
|
||||||
{:status 200
|
Long/parseLong
|
||||||
:headers {"Content-Type" "application/edn"}
|
d-clients/get-by-id
|
||||||
:body (pr-str {}) }
|
:client/code)
|
||||||
(catch Exception e
|
(Long/parseLong id)
|
||||||
(log/error e)
|
(dissoc data :client-id )))}
|
||||||
{:status 400
|
(catch Exception e
|
||||||
:headers {"Content-Type" "application/edn"}
|
(log/error e)
|
||||||
:body (pr-str {:message (.getMessage e)
|
{:status 500
|
||||||
:error (.toString e)})})))
|
:headers {"Content-Type" "application/edn"}
|
||||||
(POST "/reauthenticate/:id" {:keys [identity] {:keys [id]} :route-params
|
:body (pr-str {:message (.getMessage e)
|
||||||
data :edn-params}
|
:error (.toString e)})})))
|
||||||
(assert-admin identity)
|
|
||||||
(try
|
(defn delete-provider-account [{:keys [edn-params identity]}]
|
||||||
{:status 200
|
(assert-admin identity)
|
||||||
:headers {"Content-Type" "application/edn"}
|
(try
|
||||||
:body (pr-str (yodlee/reauthenticate-and-recache
|
(yodlee/delete-provider-account (-> (:client-id edn-params)
|
||||||
(-> (:client-id data)
|
Long/parseLong
|
||||||
Long/parseLong
|
d-clients/get-by-id
|
||||||
d-clients/get-by-id
|
:client/code)
|
||||||
:client/code)
|
(:provider-account-id edn-params))
|
||||||
(Long/parseLong id)
|
{:status 200
|
||||||
(dissoc data :client-id )))}
|
:headers {"Content-Type" "application/edn"}
|
||||||
(catch Exception e
|
:body (pr-str {}) }
|
||||||
(log/error e)
|
(catch Exception e
|
||||||
{:status 500
|
(log/error e)
|
||||||
:headers {"Content-Type" "application/edn"}
|
{:status 400
|
||||||
:body (pr-str {:message (.getMessage e)
|
:headers {"Content-Type" "application/edn"}
|
||||||
:error (.toString e)})}))))
|
:body (pr-str {:message (.getMessage e)
|
||||||
wrap-secure))
|
:error (.toString e)})})))
|
||||||
|
|
||||||
|
(defn valid-for [handler & methods]
|
||||||
|
(let [methods (into #{} methods)]
|
||||||
|
(fn [request]
|
||||||
|
(if (methods (:request-method request))
|
||||||
|
(handler request)
|
||||||
|
{:status 404}))))
|
||||||
|
|
||||||
|
(def routes {"api" {"/yodlee2" {"/fastlink" :fastlink
|
||||||
|
"/provider-accounts/refresh/" :refresh-provider-accounts
|
||||||
|
["/provider-accounts/" :client "/" :id ] :get-provider-account-detail
|
||||||
|
["/reauthenticate/" :id ] :reauthenticate
|
||||||
|
"/provider-accounts/delete/" :delete-provider-account}}})
|
||||||
|
(def match->handler {:fastlink (-> fastlink wrap-secure (valid-for :get))
|
||||||
|
:refresh-provider-accounts (-> refresh-provider-accounts wrap-secure (valid-for :post))
|
||||||
|
:get-provider-account-detail (-> get-provider-account-detail wrap-secure (valid-for :get))
|
||||||
|
:reauthenticate (-> reauthenticate wrap-secure (valid-for :post))
|
||||||
|
:delete-provider-account (-> delete-provider-account wrap-secure (valid-for :post))} )
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
(.setHandler server stats-handler))
|
(.setHandler server stats-handler))
|
||||||
(.setStopAtShutdown server true))
|
(.setStopAtShutdown server true))
|
||||||
|
|
||||||
(mount/defstate port :start (Integer/parseInt (or (env :port) "3000")))
|
(mount/defstate port :start (Integer/parseInt (or "3001" (env :port) "3000")))
|
||||||
(mount/defstate jetty
|
(mount/defstate jetty
|
||||||
:start (run-jetty app {:port port
|
:start (run-jetty app {:port port
|
||||||
:join? false
|
:join? false
|
||||||
|
|||||||
@@ -1,94 +1,16 @@
|
|||||||
(ns auto-ap.ssr.admin
|
(ns auto-ap.ssr.admin
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic :refer [conn]]
|
[auto-ap.datomic :refer [conn]]
|
||||||
[auto-ap.graphql.utils :refer [assert-admin]]
|
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.logging :as alog]
|
||||||
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
|
[auto-ap.ssr.ui :refer [base-page html-response]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.logging :as log]
|
|
||||||
[compojure.core :refer [context defroutes GET POST routes]]
|
|
||||||
[datomic.api :as d]
|
[datomic.api :as d]
|
||||||
[hiccup2.core :as hiccup]))
|
[hiccup2.core :as hiccup]))
|
||||||
|
|
||||||
(defn html-page [hiccup]
|
(defn tx-rows->changes [history]
|
||||||
{:status 200
|
|
||||||
:headers {"Content-Type" "text/html"}
|
|
||||||
:body (str
|
|
||||||
"<!DOCTYPE html>"
|
|
||||||
(hiccup/html
|
|
||||||
{}
|
|
||||||
hiccup))})
|
|
||||||
|
|
||||||
(defn base-page [contents]
|
|
||||||
(html-page
|
|
||||||
[:html.has-navbar-fixed-top
|
|
||||||
[:head
|
|
||||||
[:meta {:charset "utf-8"}]
|
|
||||||
[:meta {:http-equiv "X-UA-Compatible", :content "IE=edge"}]
|
|
||||||
[:meta {:name "viewport", :content "width=device-width, initial-scale=1"}]
|
|
||||||
[:title "Integreat"]
|
|
||||||
[:link {:rel "stylesheet", :href "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css", :integrity "sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=", :crossorigin "anonymous"}]
|
|
||||||
[:link {:href "/css/font.min.css", :rel "stylesheet"}]
|
|
||||||
[:link {:rel "stylesheet", :href "/css/bulma.min.css"}]
|
|
||||||
[:link {:rel "stylesheet", :href "/css/bulma-calendar.min.css"}]
|
|
||||||
[:link {:rel "stylesheet", :href "/css/bulma-badge.min.css"}]
|
|
||||||
[:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}]
|
|
||||||
[:link {:rel "stylesheet", :href "/css/animate.css"}]
|
|
||||||
[:link {:rel "stylesheet", :href "/finance-font/style.css"}]
|
|
||||||
[:link {:rel "stylesheet", :href "/css/main.css"}]
|
|
||||||
[:link {:rel "stylesheet", :href "https://unpkg.com/placeholder-loading/dist/css/placeholder-loading.min.css"}]
|
|
||||||
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7"}]
|
|
||||||
[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
|
||||||
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
|
||||||
:crossorigin= "anonymous"}]
|
|
||||||
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
|
||||||
|
|
||||||
|
|
||||||
[:body
|
|
||||||
[:div {:id "app"}
|
|
||||||
[:div
|
|
||||||
[:nav {:class "navbar has-shadow is-fixed-top is-grey"}
|
|
||||||
|
|
||||||
[:div {:class "container"}
|
|
||||||
[:div {:class "navbar-brand"}
|
|
||||||
[:a {:class "navbar-item", :href "../"}
|
|
||||||
[:img {:src "/img/logo.png"}]]]
|
|
||||||
[:div.navbar-menu {:id "navMenu"}
|
|
||||||
[:div.navbar-start
|
|
||||||
[:a.navbar-item {:href "/"}
|
|
||||||
"Home" ]
|
|
||||||
[:a.navbar-item {:href "/invoices/"}
|
|
||||||
"Invoices" ]
|
|
||||||
[:a.navbar-item {:href "/payments/"}
|
|
||||||
"Payments" ]
|
|
||||||
[:a.navbar-item {:href "/pos/sales-orders/"}
|
|
||||||
"POS" ]
|
|
||||||
[:a.navbar-item {:href "/transactions/"}
|
|
||||||
"Transactions" ]
|
|
||||||
|
|
||||||
[:a.navbar-item {:href "/ledger/"}
|
|
||||||
"Ledger" ]]]]]
|
|
||||||
[:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"}
|
|
||||||
[:aside {:class "column aside menu is-2 "}
|
|
||||||
[:div {:class "main left-nav"}
|
|
||||||
[:div]]]
|
|
||||||
[:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"}
|
|
||||||
[:div {:class "inbox-messages"}
|
|
||||||
contents]]]
|
|
||||||
[:div]
|
|
||||||
[:div {:id "dz-hidden"}]]]]]))
|
|
||||||
|
|
||||||
(defn html-response [hiccup]
|
|
||||||
{:status 200
|
|
||||||
:headers {"Content-Type" "text/html"}
|
|
||||||
:body (str
|
|
||||||
(hiccup/html
|
|
||||||
{}
|
|
||||||
hiccup))})
|
|
||||||
|
|
||||||
|
|
||||||
(defn inline-add-deletes [history]
|
|
||||||
(->> history
|
(->> history
|
||||||
(group-by (fn [[a _ t]]
|
(group-by (fn [[a _ t]]
|
||||||
[a t]))
|
[a t]))
|
||||||
@@ -102,6 +24,9 @@
|
|||||||
changes))]
|
changes))]
|
||||||
[t a changes])))))
|
[t a changes])))))
|
||||||
|
|
||||||
|
(def error-script
|
||||||
|
(hiccup/raw "on htmx:responseError from me set event.detail.target's innerHTML to event.detail.xhr.responseText end"))
|
||||||
|
|
||||||
(defn format-value [v]
|
(defn format-value [v]
|
||||||
(cond (inst? v)
|
(cond (inst? v)
|
||||||
(-> v
|
(-> v
|
||||||
@@ -121,29 +46,27 @@
|
|||||||
:hx-target "#history-table"}
|
:hx-target "#history-table"}
|
||||||
v]
|
v]
|
||||||
" [" [:a
|
" [" [:a
|
||||||
{:hx-get (str "/admin/history/inspect/" v)
|
{:hx-get (str "/admin/history/inspect/" v)
|
||||||
:hx-swap "innerHTML"
|
:hx-swap "innerHTML"
|
||||||
:hx-target "#inspector"
|
:hx-target "#inspector"
|
||||||
:hx-trigger "click"}
|
:hx-trigger "click"
|
||||||
"snapshot"] "]"
|
"_" error-script}
|
||||||
]
|
"snapshot"] "]"]
|
||||||
|
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(pr-str v)))
|
(pr-str v)))
|
||||||
|
|
||||||
(comment "_" )
|
|
||||||
|
|
||||||
(defn page-template [& {:keys [table entity-id]}]
|
(defn page-template [& {:keys [table entity-id]}]
|
||||||
[:div
|
[:div
|
||||||
[:div.columns
|
[:div.columns
|
||||||
[:div.column.is-4
|
[:div.column.is-4
|
||||||
[:form.hello {"hx-target" "#history-table"
|
[:form {"hx-target" "#history-table"
|
||||||
"hx-post" "/admin/history/search"
|
"hx-post" "/admin/history/search"
|
||||||
"hx-swap" "innerHTML"
|
"hx-swap" "innerHTML"
|
||||||
"_" (hiccup/raw "on htmx:beforeRequest toggle @disabled on me then toggle .is-loading on <#dig/> end
|
"_" (hiccup/raw "on htmx:beforeRequest toggle @disabled on me then toggle .is-loading on <#dig/> end
|
||||||
on htmx:afterRequest toggle @disabled on me then toggle .is-loading on <#dig /> end")
|
on htmx:afterRequest toggle @disabled on me then toggle .is-loading on <#dig /> end")
|
||||||
}
|
}
|
||||||
[:div.field.is-grouped
|
[:div.field.is-grouped
|
||||||
[:p.control {}
|
[:p.control {}
|
||||||
[:input.input {:type "text" :name "entity-id" :placeholder "Entity id" :value entity-id}]]
|
[:input.input {:type "text" :name "entity-id" :placeholder "Entity id" :value entity-id}]]
|
||||||
@@ -153,27 +76,63 @@
|
|||||||
[:div#history-table
|
[:div#history-table
|
||||||
table]])
|
table]])
|
||||||
|
|
||||||
(defn history-search [{:keys [form-params params] identity :identity :as request}]
|
(defn table [entity-id best-guess-entity history]
|
||||||
(assert-admin identity)
|
[:div [:h1.title "History for "
|
||||||
(log/info ::request
|
(str/capitalize best-guess-entity)
|
||||||
request)
|
" "
|
||||||
|
entity-id]
|
||||||
|
[:div.columns
|
||||||
|
[:div.column.is-9
|
||||||
|
[:table.table.compact.grid {:style "width: 100%"}
|
||||||
|
[:thead
|
||||||
|
[:tr
|
||||||
|
[:td {:style "width: 14em"} "Date"]
|
||||||
|
[:td {:style "width: 14em"} "User"]
|
||||||
|
[:td {:style "width: 18em"} "Field"]
|
||||||
|
[:td "From"]
|
||||||
|
[:td "To"]]]
|
||||||
|
[:tbody
|
||||||
|
(for [[tx a c] history]
|
||||||
|
[:tr
|
||||||
|
[:td [:div [:div (some-> (:db/txInstant tx)
|
||||||
|
coerce/to-date-time
|
||||||
|
atime/localize
|
||||||
|
(atime/unparse atime/standard-time))
|
||||||
|
]
|
||||||
|
[:div.tag (:db/id tx)]]]
|
||||||
|
[:td (str (:audit/user tx))]
|
||||||
|
[:td (namespace a) ": " (name a)]
|
||||||
|
|
||||||
|
[:td
|
||||||
|
[:div.tag.is-danger.is-light
|
||||||
|
[:span
|
||||||
|
(format-value (:removed c))]]]
|
||||||
|
[:td
|
||||||
|
[:div.tag.is-primary.is-light
|
||||||
|
[:span
|
||||||
|
(format-value (:added c))]]]])]
|
||||||
|
]]
|
||||||
|
[:div.column.is-3
|
||||||
|
[:div#inspector]]]])
|
||||||
|
|
||||||
|
(defn history-search [{:keys [form-params params] :as request}]
|
||||||
(try
|
(try
|
||||||
(let [entity-id (Long/parseLong (or (some-> (:entity-id form-params) not-empty)
|
(let [entity-id (Long/parseLong (or (some-> (:entity-id form-params) not-empty)
|
||||||
(:entity-id params)
|
(:entity-id params)
|
||||||
(get params "entity-id")
|
(get params "entity-id")
|
||||||
(get form-params "entity-id")))
|
(get form-params "entity-id")))
|
||||||
history (->>
|
history (->>
|
||||||
(d/q '[:find ?a2 ?v (pull ?tx [:db/txInstant :audit/user :db/id]) ?ad
|
(d/q '[:find ?a2 ?v (pull ?tx [:db/txInstant :audit/user :db/id]) ?ad
|
||||||
:in $ $$ ?i
|
:in $ $$ ?i
|
||||||
:where
|
:where
|
||||||
[$$ ?i ?a ?v ?tx ?ad]
|
[$$ ?i ?a ?v ?tx ?ad]
|
||||||
[$ ?a :db/ident ?a2]]
|
[$ ?a :db/ident ?a2]]
|
||||||
(d/db conn)
|
(d/db conn)
|
||||||
(d/history (d/db conn))
|
(d/history (d/db conn))
|
||||||
entity-id )
|
entity-id )
|
||||||
inline-add-deletes
|
tx-rows->changes
|
||||||
(sort-by (comp :db/id first))
|
(sort-by (comp :db/id first))
|
||||||
vec)
|
vec)
|
||||||
best-guess-entity (or (->> history
|
best-guess-entity (or (->> history
|
||||||
(group-by
|
(group-by
|
||||||
(comp
|
(comp
|
||||||
@@ -185,99 +144,55 @@
|
|||||||
(sort-by second)
|
(sort-by second)
|
||||||
last
|
last
|
||||||
first)
|
first)
|
||||||
"?")
|
"?")]
|
||||||
table [:div [:h1.title "History for "
|
|
||||||
(str/capitalize best-guess-entity)
|
|
||||||
" "
|
|
||||||
entity-id]
|
|
||||||
[:div.columns
|
|
||||||
[:div.column.is-9
|
|
||||||
[:table.table.compact.grid {:style "width: 100%"}
|
|
||||||
[:thead
|
|
||||||
[:tr
|
|
||||||
[:td {:style "width: 14em"} "Date"]
|
|
||||||
[:td {:style "width: 14em"} "User"]
|
|
||||||
[:td {:style "width: 18em"} "Field"]
|
|
||||||
[:td "From"]
|
|
||||||
[:td "To"]]]
|
|
||||||
[:tbody
|
|
||||||
(for [[tx a c] history]
|
|
||||||
[:tr
|
|
||||||
[:td [:div [:div (some-> (:db/txInstant tx)
|
|
||||||
coerce/to-date-time
|
|
||||||
atime/localize
|
|
||||||
(atime/unparse atime/standard-time))
|
|
||||||
]
|
|
||||||
[:div.tag (:db/id tx)]]]
|
|
||||||
[:td (str (:audit/user tx))]
|
|
||||||
[:td (namespace a) ": " (name a)]
|
|
||||||
|
|
||||||
[:td
|
|
||||||
[:div.tag.is-danger.is-light
|
|
||||||
[:span
|
|
||||||
(format-value (:removed c))]]]
|
|
||||||
[:td
|
|
||||||
[:div.tag.is-primary.is-light
|
|
||||||
[:span
|
|
||||||
(format-value (:added c))]]]])]
|
|
||||||
]]
|
|
||||||
[:div.column.is-3
|
|
||||||
[:div#inspector.box {:style {:position "sticky"
|
|
||||||
:display "inline-block"
|
|
||||||
:vertical-align "top"
|
|
||||||
:overflow-y "auto"
|
|
||||||
:max-height "100vh"
|
|
||||||
:top "0px"
|
|
||||||
:bottom "0px"}}]]]]]
|
|
||||||
|
|
||||||
(alog/info ::trace
|
|
||||||
:bge best-guess-entity
|
|
||||||
:headers (:headers request))
|
|
||||||
(if (get (:headers request) "hx-request")
|
(if (get (:headers request) "hx-request")
|
||||||
(html-response
|
(html-response
|
||||||
table)
|
(table entity-id best-guess-entity history))
|
||||||
(base-page (page-template :table table
|
(base-page (page-template :table (table entity-id best-guess-entity history)
|
||||||
:entity-id entity-id))))
|
:entity-id entity-id)
|
||||||
(catch NumberFormatException e
|
(admin-side-bar :admin-history))))
|
||||||
|
(catch NumberFormatException _
|
||||||
(html-response
|
(html-response
|
||||||
(str [:div.notification.is-danger.is-light
|
[:div.notification.is-danger.is-light
|
||||||
"Cannot parse the entity-id " (or (:entity-id form-params)
|
"Cannot parse the entity-id " (or (:entity-id form-params)
|
||||||
(:entity-id params))
|
(:entity-id params))
|
||||||
|
|
||||||
". It should be a number."])))))
|
". It should be a number."]))))
|
||||||
|
|
||||||
(defn inspect [{{:keys [entity-id]} :params identity :identity :as request}]
|
(defn inspect [{{:keys [entity-id]} :params :as request}]
|
||||||
(alog/info ::inspect
|
(alog/info ::inspect
|
||||||
:request request)
|
:request request)
|
||||||
(assert-admin identity)
|
|
||||||
(try
|
(try
|
||||||
(let [entity-id (Long/parseLong entity-id)
|
(let [entity-id (Long/parseLong entity-id)
|
||||||
data (d/pull (d/db conn)
|
data (d/pull (d/db conn)
|
||||||
'[*]
|
'[*]
|
||||||
entity-id
|
entity-id)]
|
||||||
) ]
|
|
||||||
|
|
||||||
(html-response
|
(html-response
|
||||||
[:div {:style {:display "inline-block"}}
|
[:div.box {:style {:position "sticky"
|
||||||
[:h1.title "Snapshot of "
|
:display "inline-block"
|
||||||
entity-id]
|
:vertical-align "top"
|
||||||
[:ul
|
:overflow-y "auto"
|
||||||
(for [[k v] data]
|
:max-height "100vh"
|
||||||
[:li [:strong k] ":" v]
|
:top "0px"
|
||||||
)]]))
|
:bottom "0px"}}
|
||||||
(catch NumberFormatException e
|
[:div {:style {:display "inline-block"}}
|
||||||
|
[:h1.title "Snapshot of "
|
||||||
|
entity-id]
|
||||||
|
[:ul
|
||||||
|
(for [[k v] data]
|
||||||
|
[:li [:strong k] ":" (format-value v)]
|
||||||
|
)]]]))
|
||||||
|
(catch NumberFormatException _
|
||||||
(html-response
|
(html-response
|
||||||
[:div.notification.is-danger.is-light
|
[:div.notification.is-danger.is-light
|
||||||
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
|
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
|
||||||
|
|
||||||
(defn history [{:keys [identity] :as request}]
|
(defn history [{:keys [matched-route]}]
|
||||||
(base-page (page-template )))
|
(base-page (page-template )
|
||||||
|
(admin-side-bar matched-route)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defroutes admin-routes
|
|
||||||
(routes
|
|
||||||
(context "/admin" []
|
|
||||||
(GET "/history" [] history)
|
|
||||||
(GET "/history/" [] history)
|
|
||||||
(POST "/history/search" [] history-search)
|
|
||||||
(GET "/history/:entity-id" [entity-id] history-search)
|
|
||||||
(GET "/history/inspect/:entity-id" [entity-id] inspect))))
|
|
||||||
|
|||||||
14
src/clj/auto_ap/ssr/core.clj
Normal file
14
src/clj/auto_ap/ssr/core.clj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
(ns auto-ap.ssr.core
|
||||||
|
(:require
|
||||||
|
[auto-ap.routes.utils
|
||||||
|
:refer [wrap-admin wrap-client-redirect-unauthenticated wrap-secure]]
|
||||||
|
[auto-ap.ssr.admin :as admin]
|
||||||
|
[auto-ap.ssr-routes]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; from auto-ap.ssr-routes, because they're shared
|
||||||
|
(def key->handler {:admin-history (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/history)))
|
||||||
|
:admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/history-search)))
|
||||||
|
:admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/inspect)))})
|
||||||
|
|
||||||
108
src/clj/auto_ap/ssr/ui.clj
Normal file
108
src/clj/auto_ap/ssr/ui.clj
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
(ns auto-ap.ssr.ui
|
||||||
|
(:require
|
||||||
|
[auto-ap.logging :as alog]
|
||||||
|
[config.core :refer [env]]
|
||||||
|
[hiccup2.core :as hiccup]))
|
||||||
|
|
||||||
|
(defn html-page [hiccup]
|
||||||
|
{:status 200
|
||||||
|
:headers {"Content-Type" "text/html"}
|
||||||
|
:body (str
|
||||||
|
"<!DOCTYPE html>"
|
||||||
|
(hiccup/html
|
||||||
|
{}
|
||||||
|
hiccup))})
|
||||||
|
|
||||||
|
(defn base-page [contents side-bar-contents]
|
||||||
|
(html-page
|
||||||
|
[:html.has-navbar-fixed-top
|
||||||
|
[:head
|
||||||
|
[:meta {:charset "utf-8"}]
|
||||||
|
[:meta {:http-equiv "X-UA-Compatible", :content "IE=edge"}]
|
||||||
|
[:meta {:name "viewport", :content "width=device-width, initial-scale=1"}]
|
||||||
|
[:title "Integreat"]
|
||||||
|
[:link {:rel "stylesheet", :href "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css", :integrity "sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=", :crossorigin "anonymous"}]
|
||||||
|
[:link {:href "/css/font.min.css", :rel "stylesheet"}]
|
||||||
|
[:link {:rel "stylesheet", :href "/css/bulma.min.css"}]
|
||||||
|
[:link {:rel "stylesheet", :href "/css/bulma-calendar.min.css"}]
|
||||||
|
[:link {:rel "stylesheet", :href "/css/bulma-badge.min.css"}]
|
||||||
|
[:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}]
|
||||||
|
[:link {:rel "stylesheet", :href "/css/animate.css"}]
|
||||||
|
[:link {:rel "stylesheet", :href "/finance-font/style.css"}]
|
||||||
|
[:link {:rel "stylesheet", :href "/css/main.css"}]
|
||||||
|
[:link {:rel "stylesheet", :href "https://unpkg.com/placeholder-loading/dist/css/placeholder-loading.min.css"}]
|
||||||
|
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7"}]
|
||||||
|
[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
||||||
|
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
||||||
|
:crossorigin= "anonymous"}]
|
||||||
|
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
||||||
|
|
||||||
|
|
||||||
|
[:body
|
||||||
|
[:div {:id "app"}
|
||||||
|
[:div
|
||||||
|
[:nav {:class "navbar has-shadow is-fixed-top is-grey"}
|
||||||
|
|
||||||
|
[:div {:class "container"}
|
||||||
|
[:div {:class "navbar-brand"}
|
||||||
|
[:a {:class "navbar-item", :href "../"}
|
||||||
|
[:img {:src "/img/logo.png"}]]]
|
||||||
|
[:div.navbar-menu {:id "navMenu"}
|
||||||
|
[:div.navbar-start
|
||||||
|
[:a.navbar-item {:href "/"}
|
||||||
|
"Home" ]
|
||||||
|
[:a.navbar-item {:href "/invoices/"}
|
||||||
|
"Invoices" ]
|
||||||
|
[:a.navbar-item {:href "/payments/"}
|
||||||
|
"Payments" ]
|
||||||
|
[:a.navbar-item {:href "/pos/sales-orders/"}
|
||||||
|
"POS" ]
|
||||||
|
[:a.navbar-item {:href "/transactions/"}
|
||||||
|
"Transactions" ]
|
||||||
|
|
||||||
|
[:a.navbar-item {:href "/ledger/"}
|
||||||
|
"Ledger" ]]]]]
|
||||||
|
[:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"}
|
||||||
|
[:aside {:class "column aside menu is-2 "}
|
||||||
|
[:div {:class "main left-nav"}
|
||||||
|
side-bar-contents]]
|
||||||
|
[:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"}
|
||||||
|
[:div {:class "inbox-messages"}
|
||||||
|
contents]]]
|
||||||
|
[:div]
|
||||||
|
[:div {:id "dz-hidden"}]]]]]))
|
||||||
|
|
||||||
|
(defn html-response [hiccup & {:keys [status] :or {status 200}}]
|
||||||
|
{:status status
|
||||||
|
:headers {"Content-Type" "text/html"}
|
||||||
|
:body (str
|
||||||
|
(hiccup/html
|
||||||
|
{}
|
||||||
|
hiccup))})
|
||||||
|
|
||||||
|
(defn wrap-error-response [handler]
|
||||||
|
(fn [request]
|
||||||
|
(try
|
||||||
|
(handler request)
|
||||||
|
(catch Exception e
|
||||||
|
(if-let [v (or (:validation-error (ex-data e))
|
||||||
|
(:validation-error (ex-data (.getCause e))))]
|
||||||
|
|
||||||
|
(do
|
||||||
|
(alog/warn ::request-validation-error
|
||||||
|
:exception e)
|
||||||
|
(html-response
|
||||||
|
[:div.notification.is-warning.is-light
|
||||||
|
v]
|
||||||
|
:status 400))
|
||||||
|
(do
|
||||||
|
(alog/error ::request-error
|
||||||
|
:exception e)
|
||||||
|
(when (= "dev" (:dd-env env))
|
||||||
|
(println e))
|
||||||
|
(html-response
|
||||||
|
[:div.notification.is-danger.is-light
|
||||||
|
"Server error occured."
|
||||||
|
(ex-message e)]
|
||||||
|
:status 500)))))))
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
(ns auto-ap.client-routes)
|
(ns auto-ap.client-routes)
|
||||||
|
|
||||||
(def routes ["/" {"" :index
|
(def routes ["/" {"" :index
|
||||||
"login/" :login
|
#"login/?" :login
|
||||||
"needs-activation/" :needs-activation
|
"needs-activation/" :needs-activation
|
||||||
"needs-activation" :needs-activation
|
"needs-activation" :needs-activation
|
||||||
"payments/" :payments
|
"payments/" :payments
|
||||||
@@ -42,3 +42,12 @@
|
|||||||
"balance-sheet" :balance-sheet
|
"balance-sheet" :balance-sheet
|
||||||
"external" :external-ledger
|
"external" :external-ledger
|
||||||
"external-import" :external-import-ledger}}])
|
"external-import" :external-import-ledger}}])
|
||||||
|
|
||||||
|
(defn all-handle-keys [routes]
|
||||||
|
(let [vals (vals routes)
|
||||||
|
result (filter keyword? vals)
|
||||||
|
deeper (filter map? vals)]
|
||||||
|
(apply concat result
|
||||||
|
(map all-handle-keys deeper))))
|
||||||
|
|
||||||
|
(def all-matches (set (all-handle-keys (second routes))))
|
||||||
|
|||||||
2
src/cljc/auto_ap/shared_views.cljc
Normal file
2
src/cljc/auto_ap/shared_views.cljc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
(ns auto-ap.shared-views)
|
||||||
|
|
||||||
104
src/cljc/auto_ap/shared_views/admin/side_bar.cljc
Normal file
104
src/cljc/auto_ap/shared_views/admin/side_bar.cljc
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
(ns auto-ap.shared-views.admin.side-bar
|
||||||
|
(:require [bidi.bidi :as bidi]
|
||||||
|
[auto-ap.client-routes :as client-routes]
|
||||||
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
|
#?(:cljs [re-frame.core :as re-frame])
|
||||||
|
#?(:cljs [reagent.core :as r])))
|
||||||
|
|
||||||
|
(defn active-when [active-page f & rest]
|
||||||
|
(when (apply f (into [active-page] rest)) " is-active"))
|
||||||
|
|
||||||
|
(defn deep-merge [v & vs]
|
||||||
|
(letfn [(rec-merge [v1 v2]
|
||||||
|
(if (and (map? v1) (map? v2))
|
||||||
|
(merge-with deep-merge v1 v2)
|
||||||
|
v2))]
|
||||||
|
(when (some identity vs)
|
||||||
|
(reduce #(rec-merge %1 %2) v vs))))
|
||||||
|
(def all-client-visible-routes
|
||||||
|
["/" (deep-merge ssr-routes/routes (second client-routes/routes))])
|
||||||
|
|
||||||
|
|
||||||
|
(defn menu-item [{:keys [label route test-route active-route icon-class icon-style]}]
|
||||||
|
[:p.menu-item
|
||||||
|
[:a.item {:href (bidi/path-for all-client-visible-routes route)
|
||||||
|
:class (when (test-route active-route) "is-active")}
|
||||||
|
(if icon-style
|
||||||
|
[:span {:class icon-class :style icon-style}]
|
||||||
|
[:span {:class "icon"}
|
||||||
|
[:i {:class icon-class}]])
|
||||||
|
[:span {:class "name"} label]]])
|
||||||
|
|
||||||
|
(defn admin-side-bar-impl [active-route children]
|
||||||
|
[:div
|
||||||
|
[:p.menu-label "General"]
|
||||||
|
(menu-item {:label "Dashboard"
|
||||||
|
:icon-class "fa fa-tachometer"
|
||||||
|
:test-route #{:admin}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin})
|
||||||
|
|
||||||
|
|
||||||
|
[:p.menu-label "Setup"]
|
||||||
|
(menu-item {:label "Clients"
|
||||||
|
:icon-class "fa fa-star-o"
|
||||||
|
:test-route #{:admin-clients
|
||||||
|
:admin-specific-client
|
||||||
|
:admin-specific-bank-account}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-clients})
|
||||||
|
(menu-item {:label "Vendors"
|
||||||
|
:icon-class "fa fa-star-o"
|
||||||
|
:test-route #{:admin-vendors}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-vendors})
|
||||||
|
(menu-item {:label "Users"
|
||||||
|
:icon-class "icon icon-single-neutral-book"
|
||||||
|
:test-route #{:admin-users}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-users
|
||||||
|
:icon-style {:font-size "25px"}})
|
||||||
|
(menu-item {:label "Accounts"
|
||||||
|
:icon-class "icon icon-list-bullets"
|
||||||
|
:test-route #{:admin-accounts}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-accounts
|
||||||
|
:icon-style {:font-size "25px"}})
|
||||||
|
(menu-item {:label "Rules"
|
||||||
|
:icon-class "icon icon-cog-play-1"
|
||||||
|
:test-route #{:admin-rules}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-rules
|
||||||
|
:icon-style {:font-size "25px"}})
|
||||||
|
(menu-item {:label "History"
|
||||||
|
:icon-class "icon icon-cog-play-1"
|
||||||
|
:test-route #{:admin-history :admin-history-search :admin-history-inspect}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-history
|
||||||
|
:icon-style {:font-size "25px"}})
|
||||||
|
[:p.menu-label "Import"]
|
||||||
|
(menu-item {:label "Excel Invoices"
|
||||||
|
:icon-class "fa fa-download"
|
||||||
|
:test-route #{:admin-excel-import}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-excel-import})
|
||||||
|
(menu-item {:label "Excel Invoices"
|
||||||
|
:icon-class "fa fa-download"
|
||||||
|
:test-route #{:admin-import-batches}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-import-batches})
|
||||||
|
(menu-item {:label "Background Jobs"
|
||||||
|
:icon-class "icon icon-cog-play-1"
|
||||||
|
:test-route #{:admin-jobs}
|
||||||
|
:active-route active-route
|
||||||
|
:route :admin-jobs
|
||||||
|
:icon-style {:font-size "25px"}})
|
||||||
|
(into [:div ] children)])
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defn admin-side-bar [active-page]
|
||||||
|
(admin-side-bar-impl active-page nil))
|
||||||
|
:cljs
|
||||||
|
(defn admin-side-bar []
|
||||||
|
(admin-side-bar-impl @(re-frame/subscribe [:auto-ap.subs/active-page])
|
||||||
|
(r/children (r/current-component)))))
|
||||||
8
src/cljc/auto_ap/ssr_routes.cljc
Normal file
8
src/cljc/auto_ap/ssr_routes.cljc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(ns auto-ap.ssr-routes)
|
||||||
|
|
||||||
|
(def routes {"admin" {"/history" {"" :admin-history
|
||||||
|
"/" :admin-history
|
||||||
|
#"/search/?" :admin-history-search
|
||||||
|
["/" [#"\d+" :entity-id] #"/?"] :admin-history-search
|
||||||
|
["/inspect/" [#"\d+" :entity-id] #"/?"] :admin-history-inspect}}})
|
||||||
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
(ns auto-ap.views.components.admin.side-bar
|
|
||||||
(:require
|
|
||||||
[auto-ap.routes :as routes]
|
|
||||||
[auto-ap.subs :as subs]
|
|
||||||
[auto-ap.views.utils :refer [active-when]]
|
|
||||||
[bidi.bidi :as bidi]
|
|
||||||
[re-frame.core :as re-frame]
|
|
||||||
[reagent.core :as r]))
|
|
||||||
|
|
||||||
(defn admin-side-bar []
|
|
||||||
(let [ap @(re-frame/subscribe [::subs/active-page])]
|
|
||||||
[:div
|
|
||||||
[:p.menu-label "General"]
|
|
||||||
[:p.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin) , :class (str "item" (active-when ap = :admin))}
|
|
||||||
[:span {:class "icon"}
|
|
||||||
[:i {:class "fa fa-tachometer"}]]
|
|
||||||
[:span {:class "name"} "Dashboard"]]]
|
|
||||||
|
|
||||||
[:p.menu-label "Setup"]
|
|
||||||
[:ul.menu-list
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-clients) , :class (str "item" (active-when ap = :admin-clients))}
|
|
||||||
[:span {:class "icon"}
|
|
||||||
[:i {:class "fa fa-star-o"}]]
|
|
||||||
|
|
||||||
[:span {:class "name"} "Clients"]]]
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-vendors) , :class (str "item" (active-when ap = :admin-vendors))}
|
|
||||||
[:span {:class "icon"}
|
|
||||||
[:i {:class "fa fa-star-o"}]]
|
|
||||||
|
|
||||||
[:span {:class "name"} "Vendors"]]]
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-users), :class (str "item" (active-when ap = :admin-users))}
|
|
||||||
[:span {:class "icon icon-single-neutral-book" :style {:font-size "25px"}}]
|
|
||||||
[:span {:class "name"} "Users"]]]
|
|
||||||
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-accounts), :class (str "item" (active-when ap = :admin-accounts))}
|
|
||||||
[:span {:class "icon icon-list-bullets" :style {:font-size "25px"}}]
|
|
||||||
[:span {:class "name"} "Accounts"]]]
|
|
||||||
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-rules), :class (str "item" (active-when ap = :admin-rules))}
|
|
||||||
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
|
|
||||||
[:span {:class "name"} "Rules"]]]
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (str "/admin/history") :class (str "item" (active-when ap = :admin-history))}
|
|
||||||
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
|
|
||||||
[:span {:class "name"} "History "]]]
|
|
||||||
[:ul ]]
|
|
||||||
[:p.menu-label "Import"]
|
|
||||||
[:ul.menu-list
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-excel-import) , :class (str "item" (active-when ap = :admin-excel-import))}
|
|
||||||
[:span {:class "icon"}
|
|
||||||
[:i {:class "fa fa-download"}]]
|
|
||||||
|
|
||||||
[:span {:class "name"} "Excel Invoices"]]]
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-import-batches) , :class (str "item" (active-when ap = :admin-import-batches))}
|
|
||||||
[:span {:class "icon"}
|
|
||||||
[:i {:class "fa fa-download"}]]
|
|
||||||
|
|
||||||
[:span {:class "name"} "Import Batches"]]]
|
|
||||||
[:li.menu-item
|
|
||||||
[:a {:href (bidi/path-for routes/routes :admin-jobs) :class (str "item" (active-when ap = :admin-jobs))}
|
|
||||||
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
|
|
||||||
[:span {:class "name"} "Background Jobs"]]]]
|
|
||||||
|
|
||||||
(into [:div ] (r/children (r/current-component)))]))
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
(ns auto-ap.views.pages.admin
|
(ns auto-ap.views.pages.admin
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]))
|
[auto-ap.views.components.layouts :refer [side-bar-layout]]))
|
||||||
|
|
||||||
(defn admin-page []
|
(defn admin-page []
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
(:require
|
(:require
|
||||||
[auto-ap.effects.forward :as forward]
|
[auto-ap.effects.forward :as forward]
|
||||||
[auto-ap.forms :as forms]
|
[auto-ap.forms :as forms]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.buttons :as buttons]
|
[auto-ap.views.components.buttons :as buttons]
|
||||||
[auto-ap.views.components.layouts
|
[auto-ap.views.components.layouts
|
||||||
:refer [appearing-side-bar side-bar-layout]]
|
:refer [appearing-side-bar side-bar-layout]]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[auto-ap.routes :as routes]
|
[auto-ap.routes :as routes]
|
||||||
[auto-ap.status :as status]
|
[auto-ap.status :as status]
|
||||||
[auto-ap.subs :as subs]
|
[auto-ap.subs :as subs]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.grid :as grid]
|
[auto-ap.views.components.grid :as grid]
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||||
[auto-ap.views.pages.admin.clients.form :as form]
|
[auto-ap.views.pages.admin.clients.form :as form]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[auto-ap.forms.builder :as form-builder]
|
[auto-ap.forms.builder :as form-builder]
|
||||||
[auto-ap.schema :as schema]
|
[auto-ap.schema :as schema]
|
||||||
[auto-ap.views.components :as com]
|
[auto-ap.views.components :as com]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||||
[auto-ap.views.utils :refer [with-user]]
|
[auto-ap.views.utils :refer [with-user]]
|
||||||
[malli.core :as m]
|
[malli.core :as m]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
(ns auto-ap.views.pages.admin.import-batches
|
(ns auto-ap.views.pages.admin.import-batches
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.subs :as subs]
|
[auto-ap.subs :as subs]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||||
[auto-ap.views.pages.admin.import-batches.table :as table]
|
[auto-ap.views.pages.admin.import-batches.table :as table]
|
||||||
[auto-ap.views.pages.data-page :as data-page]
|
[auto-ap.views.pages.data-page :as data-page]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
(:require
|
(:require
|
||||||
[auto-ap.status :as status]
|
[auto-ap.status :as status]
|
||||||
[auto-ap.subs :as subs]
|
[auto-ap.subs :as subs]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||||
[auto-ap.views.pages.admin.jobs.table :as table]
|
[auto-ap.views.pages.admin.jobs.table :as table]
|
||||||
[auto-ap.views.components.modal :as modal]
|
[auto-ap.views.components.modal :as modal]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[auto-ap.events :as events]
|
[auto-ap.events :as events]
|
||||||
[auto-ap.forms :as forms]
|
[auto-ap.forms :as forms]
|
||||||
[auto-ap.subs :as subs]
|
[auto-ap.subs :as subs]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.buttons :as buttons]
|
[auto-ap.views.components.buttons :as buttons]
|
||||||
[auto-ap.views.components.layouts
|
[auto-ap.views.components.layouts
|
||||||
:refer [appearing-side-bar side-bar-layout]]
|
:refer [appearing-side-bar side-bar-layout]]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[auto-ap.effects.forward :as forward]
|
[auto-ap.effects.forward :as forward]
|
||||||
[auto-ap.status :as status]
|
[auto-ap.status :as status]
|
||||||
[auto-ap.utils :refer [replace-by]]
|
[auto-ap.utils :refer [replace-by]]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.grid :as grid]
|
[auto-ap.views.components.grid :as grid]
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||||
[auto-ap.views.pages.admin.users.form :as form]
|
[auto-ap.views.pages.admin.users.form :as form]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
(:require
|
(:require
|
||||||
[auto-ap.effects.forward :as forward]
|
[auto-ap.effects.forward :as forward]
|
||||||
[auto-ap.subs :as subs]
|
[auto-ap.subs :as subs]
|
||||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
[auto-ap.shared-views.admin.side-bar :refer [admin-side-bar]]
|
||||||
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
[auto-ap.views.components.layouts :refer [side-bar-layout]]
|
||||||
[auto-ap.views.pages.admin.vendors.merge-dialog :as merge-dialog]
|
[auto-ap.views.pages.admin.vendors.merge-dialog :as merge-dialog]
|
||||||
[auto-ap.views.pages.admin.vendors.side-bar :as side-bar]
|
[auto-ap.views.pages.admin.vendors.side-bar :as side-bar]
|
||||||
|
|||||||
Reference in New Issue
Block a user