diff --git a/project.clj b/project.clj index c2fe025..61a0c60 100644 --- a/project.clj +++ b/project.clj @@ -26,7 +26,8 @@ [lein-ancient "0.6.15"] [jonase/eastwood "0.3.1"] [lein-bikeshed "0.5.1"] - [lein-cloverage "1.0.13"]]} + [lein-cloverage "1.0.13"] + [cider/cider-nrepl "LATEST"]]} :uberjar {:aot :all}} :ring {:handler wanijo.handler/app} diff --git a/src/wanijo/attribute/domain.clj b/src/wanijo/attribute/domain.clj index 8aa1b43..d8fedba 100644 --- a/src/wanijo/attribute/domain.clj +++ b/src/wanijo/attribute/domain.clj @@ -12,7 +12,7 @@ (spec/and string? types)) (spec/def ::required - #(some (partial = %) ["on" nil 0 1 "0" "1"])) + #{"on" nil 0 1 "0" "1"}) (spec/def ::created-at (spec/and string? ::neo4j/date-str)) @@ -47,13 +47,11 @@ (defn create-new! [attr schema-uuid user-uuid] (neo4j/exec-query! create-new - (-> attr - (assoc - :user_uuid user-uuid - :schema_uuid schema-uuid - :attribute_uuid (neo4j/uuid) - :created_at (neo4j/now-str)) - (update :required #(if (some? %) 1 0))))) + (assoc attr + :user_uuid user-uuid + :schema_uuid schema-uuid + :attribute_uuid (neo4j/uuid) + :created_at (neo4j/now-str)))) (neo4j/defquery edit "MATCH (a:attribute) @@ -77,3 +75,14 @@ (neo4j/exec-query! delete-by-uuid {:uuid uuid})) + +(neo4j/defquery required + "MATCH (a:attribute)-[:of]->(s:schema {uuid:{schema_uuid}}) + WHERE a.required = 1 + RETURN a + ORDER BY a.name") + +(defn required! [schema-uuid] + (map :a + (neo4j/exec-query! required + {:schema_uuid schema-uuid}))) diff --git a/src/wanijo/attribute/routes.clj b/src/wanijo/attribute/routes.clj index 9bbb914..9630cd5 100644 --- a/src/wanijo/attribute/routes.clj +++ b/src/wanijo/attribute/routes.clj @@ -12,7 +12,8 @@ (let [schema-uuid (get-in req [:params :schema])] (if (form/valid? view-schema/attr-form req) (do - (domain/create-new! (:params req) + (domain/create-new! (merge {:required 0} + (:params req)) schema-uuid (get-in req [:session :uuid])) (resp/redirect (path :schema-show {:uuid schema-uuid}))) diff --git a/src/wanijo/framework/view.clj b/src/wanijo/framework/view.clj index c5843d2..3ce0b43 100644 --- a/src/wanijo/framework/view.clj +++ b/src/wanijo/framework/view.clj @@ -59,14 +59,12 @@ [:nav (when authed? [:section.schemas - [:h2 [:span.__icon "▤"] "Created Schemas"] + [:h2 [:span.__icon "▤"] "Schemas"] [:ul - (for [schema (:created-schemas session)] - [:li (:name schema)])] - [:h2 [:span.__icon "▤"] "Other Schemas"] - [:ul - (for [schema (:other-schemas session)] - [:li (:name schema)])]])] + (for [schema (:schemas session)] + [:li [:a {:href (path :instance-list + {:schema-uuid (:uuid schema)})} + (:name schema)]])]])] (into [:main (for [msg (:flash request)] (flash-error msg))] diff --git a/src/wanijo/handler.clj b/src/wanijo/handler.clj index 9e535d2..b40f582 100644 --- a/src/wanijo/handler.clj +++ b/src/wanijo/handler.clj @@ -11,6 +11,7 @@ [wanijo.schema.middleware :as schema-middleware] [wanijo.user.routes :as user-routes] [wanijo.attribute.routes :as attr-routes] + [wanijo.instance.routes :as instance-routes] [wanijo.framework.auth :as auth] [wanijo.framework.devmode :as devmode] [wanijo.framework.routing :refer [path]])) @@ -27,7 +28,8 @@ (routes home-routes/routes schema-routes/routes user-routes/routes - attr-routes/routes)) + attr-routes/routes + instance-routes/routes)) (route/not-found "Not Found")) (def app diff --git a/src/wanijo/instance/domain.clj b/src/wanijo/instance/domain.clj new file mode 100644 index 0000000..a393019 --- /dev/null +++ b/src/wanijo/instance/domain.clj @@ -0,0 +1,56 @@ +(ns wanijo.instance.domain + (:require [clojure.spec.alpha :as spec] + [wanijo.framework.neo4j :as neo4j] + [clojure.pprint :as pprint])) + +(spec/def ::created-at ::neo4j/date-str) +(spec/def ::updated-at ::neo4j/date-str) +(spec/def ::name (spec/and (complement empty?) string?)) + +(neo4j/defquery + findy-by-schema + "MATCH (i:instance)-->(s:schema) + WHERE s.uuid = {uuid} + RETURN i + ORDER BY i.updated_at DESC") + +(defn find-by-schema! [schema-uuid] + (->> + (neo4j/exec-query! + findy-by-schema + {:uuid schema-uuid}) + (map :i))) + +(neo4j/defquery create-instance + "MATCH (s:schema {uuid:{schema_uuid}}) + CREATE (i:instance {uuid:{uuid}})-[:of]->(s) + SET i.name = {name}, + i.created_at = {created_at}, + i.updated_at = {created_at}") + +(neo4j/defquery create-property + "MATCH (i:instance {uuid:{uuid}}), + (a:attribute {uuid:{attr_uuid}}) + CREATE (p:property {uuid:{prop_uuid}})-[:of]->(i), + (p)-[:of]->(a) + SET p.value = {value}, + p.created_at = {created_at}") + +(defn create! [schema-uuid instance] + (let [instance-uuid (neo4j/uuid) + now (neo4j/now-str) + instance-tuple [create-instance + {:schema_uuid schema-uuid + :name (:name instance) + :uuid instance-uuid + :created_at now}] + prop-tuples (for [{:keys [attribute value]} (:properties instance)] + [create-property + {:uuid instance-uuid + :attr_uuid (:uuid attribute) + :prop_uuid (neo4j/uuid) + :value value + :created_at now}])] + (apply neo4j/exec-queries! + (concat [instance-tuple] + prop-tuples)))) diff --git a/src/wanijo/instance/routes.clj b/src/wanijo/instance/routes.clj new file mode 100644 index 0000000..cca174a --- /dev/null +++ b/src/wanijo/instance/routes.clj @@ -0,0 +1,66 @@ +(ns wanijo.instance.routes + (:require [compojure.core :refer [defroutes GET POST DELETE]] + [ring.util.response :as resp] + [formulare.core :as form] + [wanijo.instance.view :as view] + [wanijo.instance.domain :as domain] + [wanijo.framework.routing :refer [register! path]] + [wanijo.schema.domain :as domain-schema] + [wanijo.attribute.domain :as domain-attr])) + +(defn attr-type->widget [type] + (case type + ("markdown" "text") :textarea + :input)) + +(defn attr->field-id [attr] + (keyword (str "attr-" (:name attr)))) + +(defn attr->field [attr] + {:label (:name attr) + :required true + :widget (attr-type->widget (:type attr))}) + +(defn new-form [schema-uuid] + (update view/new-form + :fields + (fn [fields] + (reduce (fn [fields attr] + (assoc fields + (attr->field-id attr) + (attr->field attr))) + fields + (domain-attr/required! schema-uuid))))) + +(defn list! [schema-uuid req] + (view/list! (domain-schema/find-by-uuid! schema-uuid) + (domain/find-by-schema! schema-uuid) + (new-form schema-uuid) + req)) + +(defn form-data->instance [form-data required-attrs] + {:name (:name form-data) + :properties (map (fn [ra] + {:attribute ra + :value ((attr->field-id ra) form-data)}) + required-attrs)}) + +(defn new! [req] + (let [schema-uuid (get-in req [:params :schema-uuid]) + form-def (new-form schema-uuid)] + (if (form/valid? form-def req) + (let [instance (form-data->instance + (form/form-data form-def req) + (domain-attr/required! schema-uuid))] + (domain/create! schema-uuid + instance) + (resp/redirect (path :instance-list + (:params req)))) + (list! schema-uuid req)))) + +(defroutes routes + (GET (register! :instance-list "/instance/list/:schema-uuid") + [schema-uuid :as req] + (list! schema-uuid req)) + (POST (register! :instance-new "/instance/new") [] + new!)) diff --git a/src/wanijo/instance/view.clj b/src/wanijo/instance/view.clj new file mode 100644 index 0000000..7fddd9f --- /dev/null +++ b/src/wanijo/instance/view.clj @@ -0,0 +1,39 @@ +(ns wanijo.instance.view + (:require [hiccup.form :as hform] + [ring.util.anti-forgery :refer [anti-forgery-field]] + [formulare.core :as form] + [wanijo.instance.domain :as domain] + [wanijo.framework.view :as view] + [wanijo.framework.routing :refer [path]] + [wanijo.framework.time :refer [prettify-dt]])) + +(def new-form + {:fields {:name {:label "Name" + :required true + :spec ::domain/name}}}) + +(defn list! [schema instances new-form req] + (view/layout! + :request req + :content + [[:h1 "All Instances of schema " + [:span.schema-title__name (:name schema)]] + [:table + [:thead + [:tr + [:th "Name"] + [:th "Updated"] + [:th "Created"]]] + [:tbody + (for [instance instances] + [:tr + [:td + [:a (:name instance)]] + [:td (prettify-dt (:updated_at instance))] + [:td (prettify-dt (:created_at instance))]])]] + [:h1 "New Instance"] + (hform/form-to [:post (path :instance-new)] + (form/render-widgets new-form {} req) + (hform/hidden-field "schema-uuid" + (:uuid schema)) + (hform/submit-button "Create!"))])) diff --git a/src/wanijo/schema/domain.clj b/src/wanijo/schema/domain.clj index ecfe260..9339a4f 100644 --- a/src/wanijo/schema/domain.clj +++ b/src/wanijo/schema/domain.clj @@ -96,15 +96,35 @@ -[:permission {type:{type}}]- (s)) AS is_public") -(defn has-user-write-permissions? [schema-uuid user-uuid] +(defn has-user-permission? [type schema-uuid user-uuid] (let [permissions (first (neo4j/exec-query! schema-permissions {:schema_uuid schema-uuid :user_uuid user-uuid - :type "write"})) + :type type})) public? (neo4j/bool (:is_public permissions)) user? (neo4j/bool (:user_has_permission permissions))] (or public? user?))) +(defn has-user-write-permissions? [schema-uuid user-uuid] + (has-user-permission? "write" schema-uuid user-uuid)) + +(defn has-user-read-permissions? [schema-uuid user-uuid] + (has-user-permission? "read" schema-uuid user-uuid)) + +(neo4j/defquery + accessible-schemas + "MATCH (s:schema), + (u:user {uuid:{user_uuid}}) + WHERE EXISTS((u)-[:permission {type:'read'}]->(s)) + OR NOT EXISTS((:user)-[:permission {type:'read'}]->(s)) + RETURN s + ORDER BY s.name") + +(defn accessible-schemas! [user-uuid] + (map :s + (neo4j/exec-query! accessible-schemas + {:user_uuid user-uuid}))) + (neo4j/defquery delete "MATCH (s:schema) diff --git a/src/wanijo/schema/middleware.clj b/src/wanijo/schema/middleware.clj index 2a6a2ed..c2859b0 100644 --- a/src/wanijo/schema/middleware.clj +++ b/src/wanijo/schema/middleware.clj @@ -5,12 +5,7 @@ (defn wrap-user-schemas [handler] (fn [req] (if-let [uuid (get-in req [:session :uuid])] - (let [created (domain/all-created-by! uuid) - created-uids (map :uuid created) - others (filter (fn [other] - (not (in? created-uids (:uuid other)))) - (domain/all!))] - (handler (-> req - (assoc-in [:session :created-schemas] created) - (assoc-in [:session :other-schemas] others)))) + (handler (assoc-in req + [:session :schemas] + (domain/accessible-schemas! uuid))) (handler req)))) diff --git a/src/wanijo/schema/view.clj b/src/wanijo/schema/view.clj index 7a187ec..ad4a2c3 100644 --- a/src/wanijo/schema/view.clj +++ b/src/wanijo/schema/view.clj @@ -22,12 +22,14 @@ :type {:label "Type" :required true :spec ::attr-domain/type + :widget :select :options (map #(vector (capitalize %) %) attr-domain/types)} :required {:label "Required" :required false :spec ::attr-domain/required - :widget :checkbox} + :widget :checkbox + :from-req #(if (some? %) 1 0)} :uuid {:widget :hidden}}}) (def assign-form