diff --git a/.gitignore b/.gitignore index e427e975..c176da72 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ pom.xml.asc examples/ data/ \#*\# +.\#* \.terraform diff --git a/project.clj b/project.clj index 8721f0e5..07b5ceba 100644 --- a/project.clj +++ b/project.clj @@ -13,6 +13,7 @@ [org.clojure/java.jdbc "0.7.3"] [cljsjs/dropzone "4.3.0-0"] [clj-fuzzy "0.4.1"] + [com.walmartlabs/lacinia "0.25.0"] ;; https://mvnrepository.com/artifact/postgresql/postgresql [postgresql/postgresql "9.3-1102.jdbc41"] [cljs-http "0.1.44"] diff --git a/src/clj/auto_ap/db/companies.clj b/src/clj/auto_ap/db/companies.clj index bb55995a..c5a1c9c9 100644 --- a/src/clj/auto_ap/db/companies.clj +++ b/src/clj/auto_ap/db/companies.clj @@ -15,3 +15,6 @@ (defn upsert [id data] (j/update! (get-conn) :companies (clj->db data) ["id = ?" (Integer/parseInt id)] ) (parse (first (j/query (get-conn) ["SELECT * FROM companies WHERE id = ?" (Integer/parseInt id)])))) + +(defn get-by-id [id] + (parse (first (j/query (get-conn) ["SELECT * FROM companies WHERE id = ?" id])))) diff --git a/src/clj/auto_ap/db/invoices.clj b/src/clj/auto_ap/db/invoices.clj index 18cc3d0f..b578c1cb 100644 --- a/src/clj/auto_ap/db/invoices.clj +++ b/src/clj/auto_ap/db/invoices.clj @@ -5,7 +5,8 @@ [auto-ap.db.vendors :as vendors] [auto-ap.entities.companies :as company] [auto-ap.entities.vendors :as vendor] - [clojure.java.jdbc :as j])) + [clojure.java.jdbc :as j] + [clojure.string :as str])) (defn insert-multi! [rows] (j/insert-multi! (get-conn) @@ -51,6 +52,16 @@ (with-relations (map db->clj (j/query (get-conn) ["SELECT * FROM invoices WHERE (imported=false or imported is null) AND company_id = ?" (Integer/parseInt company)]))) (with-relations (map db->clj (j/query (get-conn) "SELECT * FROM invoices WHERE imported=false or imported is null"))))) +(defn query [params] + (let [ks (keys params) + sql (str " SELECT * FROM invoices " + (when (seq params) + " WHERE ") + (str/join " AND " (map (fn [k] (str (name k) " = ?")) ks))) + vs (map params ks)] + + (j/query (get-conn) (into [sql] vs)))) + (defn import [parsed-invoices companies vendors] (insert-multi! (for [{:keys [total date invoice-number customer-identifier vendor-code] :as row} parsed-invoices] diff --git a/src/clj/auto_ap/db/utils.clj b/src/clj/auto_ap/db/utils.clj index 7e20587c..68af4984 100644 --- a/src/clj/auto_ap/db/utils.clj +++ b/src/clj/auto_ap/db/utils.clj @@ -21,12 +21,15 @@ kebabed)] merged))) +(defn snake [x] + (into {} + (map + (fn [[k v]] + [(keyword (kebab->snake (name k))) v]) + x))) + (defn clj->db [x] - (let [snaked (into {} - (map - (fn [[k v]] - [(keyword (kebab->snake (name k))) v]) - x)) + (let [snaked (snake x) unmerged (if (:data snaked) (update snaked :data pr-str) snaked)] diff --git a/src/clj/auto_ap/db/vendors.clj b/src/clj/auto_ap/db/vendors.clj index 242e8e76..449cad78 100644 --- a/src/clj/auto_ap/db/vendors.clj +++ b/src/clj/auto_ap/db/vendors.clj @@ -17,6 +17,9 @@ (->> (j/query (get-conn) "SELECT * FROM vendors") (map parse))) +(defn get-by-id [id] + (parse (first (j/query (get-conn) ["SELECT * FROM vendors WHERE id = ?" id])))) + (defn upsert [id data] (j/update! (get-conn) :vendors (unparse data) ["id = ?" (Integer/parseInt id)] ) (parse (first (j/query (get-conn) ["SELECT * FROM vendors WHERE id = ?" (Integer/parseInt id)])))) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj new file mode 100644 index 00000000..7c191875 --- /dev/null +++ b/src/clj/auto_ap/graphql.clj @@ -0,0 +1,89 @@ +(ns auto-ap.graphql + (:require + [com.walmartlabs.lacinia.util :refer [attach-resolvers]] + [com.walmartlabs.lacinia.schema :as schema] + [com.walmartlabs.lacinia :refer [execute]] + [com.walmartlabs.lacinia.executor :as executor] + [com.walmartlabs.lacinia.resolve :as resolve] + [auto-ap.db.invoices :as invoices] + [auto-ap.db.vendors :as vendors] + [auto-ap.db.companies :as companies] + [auto-ap.db.utils :as utils] + [clojure.walk :as walk]) + (:import + (clojure.lang IPersistentMap))) + +(def integreat-schema + { + :objects + { + :company + {:fields {:id {:type 'Int} + :name {:type 'String}}} + :vendor + {:fields {:id {:type 'Int} + :name {:type 'String}}} + :invoice + {:fields {:id {:type 'Int} + :company_id {:type 'Int} + :vendor {:type :vendor + :resolve :get-vendor} + :company {:type :company + :resolve :get-company}}}} + :queries + {:invoice {:type '(list :invoice) + :args {:imported {:type 'Boolean} + :company_id {:type 'Int}} + + :resolve :get-invoice}}}) + +(defn by [x kf] + (reduce + (fn [m x] + (assoc m (kf x) x)) + {} + x)) + +(defn get-invoice [context args value] + (let [extra-context + (cond-> {} + (executor/selects-field? context :invoice/vendor) (assoc :vendor-cache (by (vendors/get-all) :id )) + (executor/selects-field? context :invoice/company) (assoc :company-cache (by (companies/get-all) :id )))] + (resolve/with-context (invoices/query args) extra-context))) + +(defn get-vendor [context args value] + (if-let [vendor-cache (:vendor-cache context)] + (vendor-cache (:vendor_id value)) + (vendors/get-by-id (:vendor_id value)))) + +(defn get-company [context args value] + (if-let [company-cache (:company-cache context)] + (company-cache (:company_id value)) + (companies/get-by-id (:company_id value)))) + +(def schema + (-> integreat-schema + (attach-resolvers {:get-invoice get-invoice + :get-vendor get-vendor + :get-company get-company}) + schema/compile)) + +(defn simplify + "Converts all ordered maps nested within the map into standard hash maps, and + sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems." + [m] + (walk/postwalk + (fn [node] + (cond + (instance? IPersistentMap node) + (into {} node) + + (seq? node) + (vec node) + + :else + node)) + m)) + +(defn query [q] + (simplify (execute schema q nil nil))) diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index 05471175..2654f37e 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -4,6 +4,7 @@ [auto-ap.routes.companies :as companies] [auto-ap.routes.invoices :as invoices] [auto-ap.routes.reminders :as reminders] + [auto-ap.routes.graphql :as graphql] [auto-ap.routes.vendors :as vendors] [buddy.auth.backends.token :refer [jws-backend]] [buddy.auth.middleware :refer [wrap-authentication @@ -16,7 +17,6 @@ [ring.middleware.params :refer [wrap-params]] [ring.middleware.reload :refer [wrap-reload]] [ring.util.response :as response])) -(println env) (defcredential (:aws-access-key-id env) (:aws-secret-access-key env) (:aws-region env)) @@ -31,6 +31,7 @@ companies/routes vendors/routes reminders/routes + graphql/routes auth/routes)) diff --git a/src/clj/auto_ap/routes/graphql.clj b/src/clj/auto_ap/routes/graphql.clj new file mode 100644 index 00000000..6384a106 --- /dev/null +++ b/src/clj/auto_ap/routes/graphql.clj @@ -0,0 +1,17 @@ +(ns auto-ap.routes.graphql + (:require [auto-ap.db.companies :as companies] + [auto-ap.routes.utils :refer [wrap-secure wrap-spec]] + [auto-ap.entities.companies :as entity] + [auto-ap.graphql :as ql] + [compojure.core :refer [GET PUT context defroutes + wrap-routes]])) + + +(defroutes routes + (wrap-routes + (context "/graphql" [] + (GET "/" {:keys [query-params]} + {:status 200 + :body (pr-str (ql/query (query-params "query"))) + :headers {"Content-Type" "application/edn"}})) + wrap-secure))