diff --git a/src/clj/auto_ap/datomic/transactions.clj b/src/clj/auto_ap/datomic/transactions.clj index 23422fe9..97ce69ac 100644 --- a/src/clj/auto_ap/datomic/transactions.clj +++ b/src/clj/auto_ap/datomic/transactions.clj @@ -37,6 +37,12 @@ :where ['[?e :transaction/bank-account ?bank-account-id]]} :args [(:bank-account-id args)]}) + (:account-id args) + (merge-query {:query {:in ['?account-id] + :where ['[?e :transaction/accounts ?accounts] + '[?accounts :transaction-account/account ?account-id]]} + :args [(:account-id args)]}) + (:client-id args) (merge-query {:query {:in ['?client-id] :where ['[?e :transaction/client ?client-id]]} diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index bd66643f..9dc59c99 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -590,6 +590,7 @@ :transaction_filters {:fields {:client_id {:type :id} :vendor_id {:type :id} :bank_account_id {:type :id} + :account_id {:type :id} :date_range {:type :date_range} :amount_lte {:type :money} :amount_gte {:type :money} diff --git a/src/clj/auto_ap/graphql/transaction_rules.clj b/src/clj/auto_ap/graphql/transaction_rules.clj index 2cf7758e..031f4ff0 100644 --- a/src/clj/auto_ap/graphql/transaction_rules.clj +++ b/src/clj/auto_ap/graphql/transaction_rules.clj @@ -85,8 +85,7 @@ (when (not (get (into #{"Shared"} (:client/locations client)) (:location a))) (let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client " location)] - (throw (ex-info err {:validation-error err}) ))) - ) + (throw (ex-info err {:validation-error err}) )))) rule-id (if id id "transaction-rule") diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index 2f072e63..93612192 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -1,48 +1,56 @@ (ns auto-ap.handler (:require [amazonica.core :refer [defcredential]] [auto-ap.routes.auth :as auth] - [auto-ap.routes.clients :as clients] - [auto-ap.routes.invoices :as invoices] - [auto-ap.routes.reminders :as reminders] - [auto-ap.routes.graphql :as graphql] - [auto-ap.routes.yodlee :as yodlee] [auto-ap.routes.events :as events] - [auto-ap.routes.checks :as checks] [auto-ap.routes.exports :as exports] + [auto-ap.routes.graphql :as graphql] + [auto-ap.routes.invoices :as invoices] + [auto-ap.routes.yodlee :as yodlee] [buddy.auth.backends.token :refer [jws-backend]] - [buddy.auth.middleware :refer [wrap-authentication - wrap-authorization]] - [clojure.java.jdbc :as jdbc] + [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]] + [clojure.tools.logging :as log] [compojure.core :refer :all] [compojure.route :as route] [config.core :refer [env]] - [clojure.tools.logging :as log] - [unilog.context :as lc] + [mount.core :as mount] [ring.middleware.edn :refer [wrap-edn-params]] + [ring.middleware.gzip :refer [wrap-gzip]] [ring.middleware.multipart-params :as mp] [ring.middleware.params :refer [wrap-params]] [ring.middleware.reload :refer [wrap-reload]] - [ring.middleware.gzip :refer [wrap-gzip]] - [ring.util.response :as response])) + [ring.util.response :as response] + [unilog.context :as lc])) (when (:aws-access-key-id env) (defcredential (:aws-access-key-id env) (:aws-secret-access-key env) (:aws-region env))) +(def running? (atom false)) + +(mount/defstate manage-running? + :start (reset! running? true) + :stop (reset! running? false)) + (defroutes static-routes (GET "/" [] (response/resource-response "index.html" {:root "public"})) (route/resources "/") (routes (ANY "*" [] (response/resource-response "index.html" {:root "public"})))) +(defroutes health-check + (GET "/health-check" [] + (if @running? + {:status 200 + :body "Ok"} + {:status 503 + :body "Application shut down"}))) + (defroutes api-routes (context "/api" [] exports/routes yodlee/routes invoices/routes - clients/routes - reminders/routes - checks/routes graphql/routes - auth/routes)) + auth/routes + health-check)) (def auth-backend (jws-backend {:secret (:jwt-secret env) :options {:alg :hs512}})) diff --git a/src/clj/auto_ap/routes/checks.clj b/src/clj/auto_ap/routes/checks.clj deleted file mode 100644 index 9bab0b8c..00000000 --- a/src/clj/auto_ap/routes/checks.clj +++ /dev/null @@ -1,9 +0,0 @@ -(ns auto-ap.routes.checks - (:require - [hiccup.core :refer [html]] - [auto-ap.routes.utils :refer [wrap-secure]] - [compojure.core :refer [GET POST context defroutes wrap-routes]])) -(defroutes routes - (wrap-routes - (context "/checks" []) - wrap-secure)) diff --git a/src/clj/auto_ap/routes/clients.clj b/src/clj/auto_ap/routes/clients.clj deleted file mode 100644 index a4025eee..00000000 --- a/src/clj/auto_ap/routes/clients.clj +++ /dev/null @@ -1,29 +0,0 @@ -(ns auto-ap.routes.clients - (:require [auto-ap.datomic.clients :as clients] - [auto-ap.graphql.utils :refer [can-see-client? assert-can-see-client]] - [auto-ap.routes.utils :refer [wrap-secure wrap-spec]] - [auto-ap.entities.clients :as entity] - [compojure.core :refer [GET PUT context defroutes - wrap-routes]])) - - -(defroutes routes - (wrap-routes - (context "/clients" [] - #_(wrap-spec - (PUT "/:id" {{:keys [address email locations new-bank-accounts]} :edn-params :keys [edn-params] {:keys [id ]} :route-params :as r} - (assert-can-see-client (:identity r) id) - (let [id (Integer/parseInt id) - company (d-clients/get-by-id id) - updated-company (merge company {:address address - :email email - :locations locations})] - #_(companies/upsert id updated-company) - #_(doseq [bank-account new-bank-accounts] - (companies/add-bank-account id bank-account)) - - {:status 200 - :body (pr-str (clients/get-by-id id)) - :headers {"Content-Type" "application/edn"}})) - ::entity/company)) - wrap-secure)) diff --git a/src/clj/auto_ap/routes/reminders.clj b/src/clj/auto_ap/routes/reminders.clj deleted file mode 100644 index 455fce97..00000000 --- a/src/clj/auto_ap/routes/reminders.clj +++ /dev/null @@ -1,100 +0,0 @@ -(ns auto-ap.routes.reminders - (:require - [auto-ap.routes.utils :refer [wrap-secure]] - [auto-ap.graphql.utils :refer [assert-admin]] - [config.core :refer [env]] - [clj-http.client :as http] - [clj-time.coerce :as c] - [clj-time.core :as time] - [clj-time.periodic :as p] - [clj-time.predicates :as pred] - [clojure.data.json :as json] - [compojure.core :refer [GET PUT POST context defroutes - wrap-routes]]) - (:import (org.joda.time DateTime))) - -#_(POST "/:id/remind" {:keys [edn-params] {:keys [id :<< as-int]} :route-params :as r} - (assert-admin (:identity r)) - (let [id (if (int? id) - id - (Integer/parseInt id)) - vendor (vendors/get-by-id id)] - (reminders/insert (assoc - (reminders/template) - :email (:primary-email vendor) - :vendor-id id - :scheduled (time/now))) - (-> (reminders/get-ready) - (reminders/send-emails)) - {:status 200 - :body "{}" - :headers {"Content-Type" "application/edn"}})) -#_(defn next-sunday [] - (let [sunday (->> (p/periodic-seq (time/plus (time/today) (time/days 1)) (time/days 1)) - (filter pred/sunday?) - first)] - (time/from-time-zone (time/date-time (time/year sunday) (time/month sunday) (time/day sunday)) - (time/time-zone-for-id "America/Los_Angeles")))) - -#_(defn schedule-reminders [] - (let [vendors (vendors/find-with-reminders) - future-reminders (reminders/find-future (map :id vendors)) - has-reminder-scheduled? (set (map :vendor-id future-reminders)) - vendors-without-scheduled (filter #(not (has-reminder-scheduled? (:id %))) vendors)] - (println "Reminders already scheduled:" future-reminders) - (println "Reminders will happen at" (next-sunday)) - (println "Reminders to schedule" vendors-without-scheduled) - - (doseq [{:keys [id primary-email invoice-reminder-schedule]} vendors-without-scheduled] - (reminders/insert (assoc (reminders/template) - :vendor-id id - :email primary-email - :scheduled (next-sunday)))))) - -#_(defn find-ready-reminders [] - (let [vendors (vendors/get-all) - ready-reminders (reminders/get-ready)] - ready-reminders)) - - - -#_(defn replace-joda [x] - (into {} (map (fn [[k v]] - [k (if (instance? DateTime v) - (c/to-date v) - v)]) - x))) - -(defroutes routes - (context "/reminders" [] - - #_(POST "/send" {:keys [query-params headers body] :as x} - (let [notification-type (get headers "x-amz-sns-message-type")] - (println "Received notification " notification-type) - (if (= "SubscriptionConfirmation" notification-type) - (do - (println "Responding to confirmation" ) - (let [json (json/read-str (slurp body))] - (println json) - (http/get (get json "SubscribeURL")))) - (do - (println "Scheduling") - (schedule-reminders) - (-> (reminders/get-ready) - (reminders/send-emails))))) - - {:status 200 - :body "{}" - :headers {"Content-Type" "application/edn"}}) - #_(wrap-routes - (PUT "/:id" {:keys [ edn-params] {:keys [id] } :route-params identity :identity} - (assert-admin identity) - (let [id (if (int? id) - id - (Integer/parseInt id))] - (assert (not (:sent (reminders/get-by-id id)))) - (reminders/update! id edn-params) - {:status 200 - :body "{}" - :headers {"Content-Type" "application/edn"}})) - wrap-secure))) diff --git a/src/clj/auto_ap/server.clj b/src/clj/auto_ap/server.clj index cfc46945..c7cacad8 100644 --- a/src/clj/auto_ap/server.clj +++ b/src/clj/auto_ap/server.clj @@ -14,14 +14,19 @@ [mount.core :as mount]) (:gen-class)) - +(defn add-shutdown-hook! [^Runnable f] + (.addShutdownHook (Runtime/getRuntime) + (Thread. f))) (mount/defstate port :start (Integer/parseInt (or (env :port) "3000"))) (mount/defstate jetty :start (run-jetty app {:port port :join? false}) - :stop (.stop jetty) - ) + :stop (.stop jetty)) + +(defn shutdown-mount [] + (mount/stop)) (defn -main [& args] + (add-shutdown-hook! shutdown-mount) (start-server :port 9000 :bind "0.0.0.0" #_#_:handler (cider-nrepl-handler)) (mount/start)) diff --git a/src/cljs/auto_ap/views/pages/ledger/side_bar.cljs b/src/cljs/auto_ap/views/pages/ledger/side_bar.cljs index 1ca2bc4f..df16976b 100644 --- a/src/cljs/auto_ap/views/pages/ledger/side_bar.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/side_bar.cljs @@ -49,6 +49,17 @@ :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :bank-account %]) :value @(re-frame/subscribe [::data-page/filter data-page :bank-account])}]] + + + [:p.menu-label "Financial Account"] + [:div + [typeahead-entity {:matches accounts + :match->text (fn [x ] (str (:numeric-code x) " - " (:name x))) + :include-keys [:name :id :numeric-code] + :type "typeahead-entity" + :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :account %]) + :value @(re-frame/subscribe [::data-page/filter data-page :account])}]] + [:p.menu-label "Vendor"] [:div [typeahead-entity {:matches @(re-frame/subscribe [::subs/searchable-vendors]) @@ -58,22 +69,15 @@ :type "typeahead-entity" :value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]] - [:p.menu-label "Account"] - [:div - [typeahead-entity {:matches accounts - :match->text (fn [x ] (str (:numeric-code x) " - " (:name x))) - :include-keys [:name :id :numeric-code] - :type "typeahead-entity" - :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :account %]) - :value @(re-frame/subscribe [::data-page/filter data-page :account])}]] - [:p.menu-label "Amount"] - [:div - [number-filter - {:on-change-event [::data-page/filter-changed data-page :amount-range] - :value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]] + [:p.menu-label "Date Range"] [:div [date-range-filter {:on-change-event [::data-page/filter-changed data-page :date-range] - :value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]]])]])) + :value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]] + [:p.menu-label "Amount"] + [:div + [number-filter + {:on-change-event [::data-page/filter-changed data-page :amount-range] + :value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]]])]])) diff --git a/src/cljs/auto_ap/views/pages/transactions.cljs b/src/cljs/auto_ap/views/pages/transactions.cljs index 56585f45..1e617d06 100644 --- a/src/cljs/auto_ap/views/pages/transactions.cljs +++ b/src/cljs/auto_ap/views/pages/transactions.cljs @@ -28,6 +28,7 @@ :client-id (:id @(re-frame/subscribe [::subs/client])) :vendor-id (:id (:vendor params)) :date-range (:date-range params) + :account-id (:id (:account params)) :bank-account-id (:id (:bank-account params)) :amount-gte (:amount-gte (:amount-range params)) :amount-lte (:amount-lte (:amount-range params)) diff --git a/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs b/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs index 37ee9815..ed91c22b 100644 --- a/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/side_bar.cljs @@ -10,11 +10,10 @@ [re-frame.core :as re-frame] [auto-ap.views.pages.data-page :as data-page])) - - (defn side-bar [{:keys [data-page]}] (let [ap @(re-frame/subscribe [::subs/active-page]) - user @(re-frame/subscribe [::subs/user])] + user @(re-frame/subscribe [::subs/user]) + accounts @(re-frame/subscribe [::subs/accounts])] [:div [:div [:p.menu-label "Type"] [:ul.menu-list @@ -57,11 +56,25 @@ :value @(re-frame/subscribe [::data-page/filter data-page :bank-account]) :bank-accounts @(re-frame/subscribe [::subs/bank-accounts])}]] - [:p.menu-label "Date Range"] - [:div - [date-range-filter - {:on-change-event [::data-page/filter-changed data-page :date-range] - :value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]] + [:p.menu-label "Financial Account"] + [:div + [typeahead-entity {:matches accounts + :match->text (fn [x ] (str (:numeric-code x) " - " (:name x))) + :include-keys [:name :id :numeric-code] + :type "typeahead-entity" + :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :account %]) + :value @(re-frame/subscribe [::data-page/filter data-page :account])}]] + + [:p.menu-label "Vendor"] + [:div + [typeahead-entity {:matches @(re-frame/subscribe [::subs/searchable-vendors]) + :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :vendor %]) + :include-keys [:name :id] + :match->text :name + :type "typeahead-entity" + :value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]] + + [:p.menu-label "Amount"] [:div @@ -69,14 +82,13 @@ {:on-change-event [::data-page/filter-changed data-page :amount-range] :value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]] - [:p.menu-label "Vendor"] - [:div - [typeahead-entity {:matches @(re-frame/subscribe [::subs/searchable-vendors]) - :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :vendor %]) - :include-keys [:name :id] - :match->text :name - :type "typeahead-entity" - :value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]] + [:p.menu-label "Date Range"] + [:div + [date-range-filter + {:on-change-event [::data-page/filter-changed data-page :date-range] + :value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]] + + [:p.menu-label "Description"] [:div