diff --git a/src/snippets/infra/api.clj b/src/snippets/infra/api.clj index 14f8a37..65b87eb 100644 --- a/src/snippets/infra/api.clj +++ b/src/snippets/infra/api.clj @@ -38,12 +38,7 @@ {:status 200 :body (snippets.use-cases.view/view-snippets {:limit limit-num :skip skip-num})}) {:status 200 - :body (snippets.use-cases.view/view-snippets)})) - -(defn handle-view-snippet [{params :query-params}] - (let [id (get params "id")] - {:status 200 - :body (snippets.use-cases.view/view-snippet id)})) + :body (snippets.use-cases.view/view-snippets nil)})) (defn handle-delete-snippet [{params :query-params}] (let [id (get params "id")] @@ -82,7 +77,6 @@ ["/snippet-by-slug" {:get handle-view-snippet-by-slug}] ["/snippets" {:get handle-view-snippets}] ["/snippet" {:post handle-create-snippet - :get handle-view-snippet :patch handle-edit-snippet :delete handle-delete-snippet}]]) (rr/create-default-handler))) diff --git a/src/snippets/infra/db2.clj b/src/snippets/infra/db2.clj index 37ef082..9df0f27 100644 --- a/src/snippets/infra/db2.clj +++ b/src/snippets/infra/db2.clj @@ -7,20 +7,21 @@ ;; Initialize the Datomic Local client ;; :system "dev" groups your databases in the "dev" system ;; In production, you'd set :storage-dir to a persistent path +;; TODO: add save file location for prod (def client (d/client {:server-type :datomic-local :system "dev"})) (def db-name "snippets") ;; Create the database if it doesn't exist -(defn ensure-db +(defn- ensure-db "Check if db exists, create it if not." [] (d/create-database client {:db-name db-name}) (t/log! {:level :info} "Snippets database created if needed")) ;; Get a connection to the database -(defn get-conn [] +(defn- get-conn [] (d/connect client {:db-name db-name})) ;; Define the schema for snippets @@ -43,18 +44,20 @@ :db/valueType :db.type/instant :db/cardinality :db.cardinality/one}]) -(defn ensure-schema +(defn- ensure-schema "Transact the schema if it doesn't exist. Call this once on startup." [] (let [conn (get-conn)] (d/transact conn {:tx-data snippet-schema}) (t/log! {:level :info} "Snippet schema created if needed"))) -(defn start-up-check [] +(defn start-up-check + "Should be run at startup to ensure the database and schema are created." + [] (ensure-db) (ensure-schema)) -(defn snippet-to-entity +(defn- snippet-to-entity "Convert a snippet map to a Datomic DB entity." [snippet] {:snippet/title (:title snippet) @@ -63,6 +66,26 @@ :snippet/tags (:tags snippet) :snippet/pub-date (:pub-date snippet)}) +(defn- entity-to-snippet + "Convert a Datomic DB entity to a snippet map." + [entity] + {:title (:snippet/title entity) + :slug (:snippet/slug entity) + :markdown (:snippet/markdown entity) + :tags (:snippet/tags entity) + :pub-date (:snippet/pub-date entity)}) + +(defn- wrap-snippet-return + "Wraps an fn that returns snippet, snippet[], or nil; converting the entity to a snippet map." + [snippet-fn] + (fn [& args] + (let [res (apply snippet-fn args)] + (cond + (nil? res) nil + :else (if (sequential? res) + (map entity-to-snippet res) + (entity-to-snippet res)))))) + ;; create (def create-schema "Malli schema for a valid snippet entity creation." @@ -73,12 +96,12 @@ [:snippet/tags [:vector :string]] [:snippet/pub-date [:fn #(instance? java.util.Date %)]]]) -(defn valid-create? +(defn- valid-create? "Check if a snippet map is a valid Datomic entity." [entity] (m/validate create-schema entity)) -(defn put-snippets +(defn- put-snippets "Create new snippets in the database." [snippets] (t/log! {:level :info, :data {:slugs (map :slug snippets)}} "Saving new snippets to db") @@ -88,8 +111,11 @@ (d/transact conn {:tx-data entities}) (throw (ex-info "Invalid snippet entity" {:entities entities}))))) +(def create-snippets + (wrap-snippet-return put-snippets)) + ;; read -(defn get-snippet-by-slug +(defn- get-snippet-by-slug-from-db "Get a single snippet by its slug." [slug] (let [conn (get-conn) @@ -99,6 +125,9 @@ :where [?e :snippet/slug ?slug]]] (ffirst (d/q query db slug)))) +(def get-snippet-by-slug + (wrap-snippet-return get-snippet-by-slug-from-db)) + ;; update (def update-schema "Malli schema for a valid update to a snippet entity." @@ -108,14 +137,14 @@ [:snippet/markdown {:optional true} :string] [:snippet/tags {:optional true} [:vector :string]]]) -(defn to-update [patch] +(defn- to-update [patch] (cond-> {} (some? (:title patch)) (assoc :snippet/title (:title patch)) (some? (:slug patch)) (assoc :snippet/slug (:slug patch)) (some? (:markdown patch)) (assoc :snippet/markdown (:markdown patch)) (some? (:tags patch)) (assoc :snippet/tags (:tags patch)))) -(defn patch-snippet +(defn- patch-snippet-in-db "Update specific fields of a snippet." [slug raw-patch] (let [conn (get-conn) @@ -129,7 +158,9 @@ (let [tx-data [(merge {:db/id eid} patch)]] (d/transact conn {:tx-data tx-data})))) -(defn list-snippets +(def update-snippet (wrap-snippet-return patch-snippet-in-db)) + +(defn list-snippets-in-db "List all the snippets" [] (let [conn (get-conn) @@ -140,12 +171,13 @@ (->> (d/q query db) (map first)))) +(def list-snippets (wrap-snippet-return list-snippets-in-db)) + (defn delete-snippet-by-slug "Soft delete a snippet (retract its entity)." [slug] (t/log! {:level :info, :data {:slug slug}} "Retracting snippet") (let [conn (get-conn) - db (d/db conn) eid (:db/id (get-snippet-by-slug slug))] (when (nil? eid) (throw (ex-info "Snippet not found" {:slug slug}))) @@ -161,7 +193,7 @@ [?e :snippet/tags ?tag]]] (d/q query db))) -(defn get-snippets-by-tag +(defn get-snippets-by-tag-in-db "Get all snippets that have a specific tag." [tag] (let [conn (get-conn) @@ -172,3 +204,5 @@ [?e :snippet/tags ?tag]] results (d/q query db tag)] (mapv first results))) + +(def get-snippets-by-tag (wrap-snippet-return get-snippets-by-tag-in-db)) diff --git a/src/snippets/use_cases/view.clj b/src/snippets/use_cases/view.clj index 9626152..5d7a936 100644 --- a/src/snippets/use_cases/view.clj +++ b/src/snippets/use_cases/view.clj @@ -1,7 +1,7 @@ (ns snippets.use-cases.view (:require [taoensso.telemere :as t] - [snippets.infra.db :as db])) + [snippets.infra.db2 :as db])) (defn serialize-snippet "Converts snippet pub-date to ISO-8601 string for EDN serialization" @@ -9,12 +9,15 @@ (when snippet (assoc snippet :pub-date (.toString (:pub-date snippet))))) -(defn view-snippet [key] - (t/log! {:level :info, :data {:key key}} "Viewing snippet by id") - (serialize-snippet (db/get-snippet-by-id key))) - -(defn view-snippets [& args] - (map serialize-snippet (db/list-snippets args))) +(defn view-snippets [options] + (if (nil? options) + (map serialize-snippet (db/list-snippets)) + (let [limit (:limit options) + skip (:skip options)] + (->> (db/list-snippets) + (drop skip) + (take limit) + (map serialize-snippet))))) (defn view-tags [] (t/log! {:level :info} "Viewing tags")