move db2 to db
This commit is contained in:
parent
04520a5790
commit
ca13eb6edc
9 changed files with 213 additions and 286 deletions
5
deps.edn
5
deps.edn
|
|
@ -6,10 +6,7 @@
|
||||||
|
|
||||||
;; db
|
;; db
|
||||||
com.datomic/local {:mvn/version "1.0.291"}
|
com.datomic/local {:mvn/version "1.0.291"}
|
||||||
com.xtdb/xtdb-api {:mvn/version "2.0.0-beta9"}
|
|
||||||
com.github.seancorfield/next.jdbc {:mvn/version "1.3.1002"}
|
|
||||||
org.postgresql/postgresql {:mvn/version "42.7.6"}
|
|
||||||
frontmatter/frontmatter {:mvn/version "0.0.1"}
|
|
||||||
;; logging
|
;; logging
|
||||||
com.taoensso/telemere {:mvn/version "1.0.0"}
|
com.taoensso/telemere {:mvn/version "1.0.0"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,219 @@
|
||||||
(ns snippets.infra.db
|
(ns snippets.infra.db
|
||||||
(:require
|
(:require
|
||||||
[taoensso.telemere :as t]
|
[clojure.set :as set]
|
||||||
|
[datomic.client.api :as d]
|
||||||
|
[malli.core :as m]
|
||||||
[snippets.infra.config :as config]
|
[snippets.infra.config :as config]
|
||||||
[xtdb.api :as xt]))
|
[taoensso.telemere :as t]))
|
||||||
|
|
||||||
(def client
|
;; Initialize the Datomic Local client
|
||||||
(let
|
;; :system "dev" groups your databases in the "dev" system
|
||||||
[c (:xtdb (config/get-config))]
|
;; In production, you'd set :storage-dir to a persistent path
|
||||||
(xt/client
|
;; TODO: add save file location for prod
|
||||||
{:host (:host c)
|
|
||||||
:port (:port c)
|
|
||||||
:user "xtdb"
|
|
||||||
;; :password "xtdb"
|
|
||||||
:dbname "xtdb"})))
|
|
||||||
|
|
||||||
;; xtdb query docs: https://docs.xtdb.com/reference/main/xtql/queries.html#_limit
|
(def datomic-config (:datomic (config/get-config)))
|
||||||
(defn list-snippets [{:keys [skip limit]}]
|
|
||||||
(if (nil? limit)
|
|
||||||
(xt/q client
|
|
||||||
'(-> (from :snippets [title pub-date tags slug markdown {:xt/id id}]) (order-by {:val pub-date, :dir :desc, :nulls :last})))
|
|
||||||
(xt/q client
|
|
||||||
(eval
|
|
||||||
(read-string
|
|
||||||
(format "(quote (-> (from :snippets [title pub-date tags slug markdown {:xt/id id}]) (order-by {:val pub-date, :dir :desc, :nulls :last}) (offset %s) (limit %s)))" skip limit))))))
|
|
||||||
|
|
||||||
(defn get-snippet-by-id [snippet-id]
|
(def client (d/client (merge {:server-type :datomic-local
|
||||||
(first (xt/q client ['#(from :snippets [{:xt/id %} slug title tags {:xt/id id} markdown pub-date]) snippet-id])))
|
:system "dev"} datomic-config)))
|
||||||
|
|
||||||
(defn get-snippet-by-slug [slug]
|
(def db-name "snippets")
|
||||||
(first (xt/q client ['#(from :snippets [{:xt/id id} {:slug %} slug title tags markdown pub-date]) slug])))
|
|
||||||
|
|
||||||
(defn put-snippet [id snippet]
|
;; Create the database if it doesn't exist
|
||||||
(t/log! {:level :info, :data {:snippet snippet :id id}} "Saving new snippet to db")
|
(defn- ensure-db
|
||||||
(xt/execute-tx client [[:put-docs :snippets (merge snippet {:xt/id id})]]))
|
"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"))
|
||||||
|
|
||||||
(defn delete-snippet [id]
|
;; Get a connection to the database
|
||||||
(t/log! {:level :info, :data {:id id}} "Deleting snippet")
|
(defn- get-conn []
|
||||||
(xt/execute-tx client [[:delete-docs :snippets id]]))
|
(d/connect client {:db-name db-name}))
|
||||||
|
|
||||||
(defn erase-snippet [id]
|
;; Define the schema for snippets
|
||||||
(t/log! {:level :info, :data {:id id}} "Erasing snippet, aka removing it completely through out time")
|
;; Transact this once to set up the database structure
|
||||||
(xt/execute-tx client [[:erase-docs :snippets id]]))
|
(def snippet-schema
|
||||||
|
[{:db/ident :snippet/title
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
{:db/ident :snippet/slug
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/unique :db.unique/value}
|
||||||
|
{:db/ident :snippet/markdown
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one}
|
||||||
|
{:db/ident :snippet/tags
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/many}
|
||||||
|
{:db/ident :snippet/pub-date
|
||||||
|
:db/valueType :db.type/instant
|
||||||
|
:db/cardinality :db.cardinality/one}])
|
||||||
|
|
||||||
(defn patch-snippet [id patch]
|
(defn- ensure-schema
|
||||||
(t/log! {:level :info, :data {:patch patch :id id}} "Patching snippet")
|
"Transact the schema if it doesn't exist. Call this once on startup."
|
||||||
(xt/execute-tx client [[:patch-docs :snippets (merge {:xt/id id} patch)]]))
|
[]
|
||||||
|
(let [conn (get-conn)]
|
||||||
|
(d/transact conn {:tx-data snippet-schema})
|
||||||
|
(t/log! {:level :info} "Snippet schema created if needed")))
|
||||||
|
|
||||||
(defn list-tags []
|
(defn start-up-check
|
||||||
(xt/q client '(-> (from :snippets [{:xt/id id} tags]) (unnest {:tag tags}) (without :tags) (aggregate tag {:count (count id)}))))
|
"Should be run at startup to ensure the database and schema are created."
|
||||||
|
[]
|
||||||
|
(ensure-db)
|
||||||
|
(ensure-schema))
|
||||||
|
|
||||||
(defn get-snippets-by-tag [tag]
|
(defn- snippet-to-entity
|
||||||
(map #(dissoc % :tag)
|
"Convert a snippet map to a Datomic DB entity."
|
||||||
(xt/q client (eval (read-string
|
[snippet]
|
||||||
(format "(quote (-> (from :snippets [title slug tags pub-date]) (unnest {:tag tags}) (without :tags) (where (= tag \"%s\"))))" tag))))))
|
{:snippet/title (:title snippet)
|
||||||
|
:snippet/slug (:slug snippet)
|
||||||
|
:snippet/markdown (:markdown snippet)
|
||||||
|
: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."
|
||||||
|
[:map
|
||||||
|
[:snippet/title :string]
|
||||||
|
[:snippet/slug :string]
|
||||||
|
[:snippet/markdown :string]
|
||||||
|
[:snippet/tags [:vector :string]]
|
||||||
|
[:snippet/pub-date [:fn #(instance? java.util.Date %)]]])
|
||||||
|
|
||||||
|
(defn- valid-create?
|
||||||
|
"Check if a snippet map is a valid Datomic entity."
|
||||||
|
[entity]
|
||||||
|
(m/validate create-schema entity))
|
||||||
|
|
||||||
|
(defn create-snippets
|
||||||
|
"Create new snippets in the database."
|
||||||
|
[snippets]
|
||||||
|
(t/log! {:level :info, :data {:slugs (map :slug snippets)}} "Saving new snippets to db")
|
||||||
|
(let [conn (get-conn)
|
||||||
|
entities (map snippet-to-entity snippets)]
|
||||||
|
(if (every? valid-create? entities)
|
||||||
|
(d/transact conn {:tx-data entities})
|
||||||
|
(throw (ex-info "Invalid snippet entity" {:entities entities})))))
|
||||||
|
|
||||||
|
;; read
|
||||||
|
(defn- get-snippet-by-slug-from-db
|
||||||
|
"Get a single snippet by its slug."
|
||||||
|
[slug]
|
||||||
|
(let [conn (get-conn)
|
||||||
|
db (d/db conn)
|
||||||
|
query '[:find (pull ?e [*])
|
||||||
|
:in $ ?slug
|
||||||
|
:where [?e :snippet/slug ?slug]]
|
||||||
|
snippet (ffirst (d/q query db slug))]
|
||||||
|
(t/log! {:level :info, :data {:slug slug :snippet snippet}} "Got snippet by slug")
|
||||||
|
snippet))
|
||||||
|
|
||||||
|
(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."
|
||||||
|
[:map
|
||||||
|
[:db/id :int]
|
||||||
|
[:snippet/title {:optional true} :string]
|
||||||
|
[:snippet/slug {:optional true} :string]
|
||||||
|
[:snippet/markdown {:optional true} :string]
|
||||||
|
[:snippet/tags {:optional true} [:vector :string]]])
|
||||||
|
|
||||||
|
(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-in-db
|
||||||
|
"Update specific fields of a snippet."
|
||||||
|
[slug raw-patch]
|
||||||
|
(let [conn (get-conn)
|
||||||
|
snippet (get-snippet-by-slug-from-db slug)
|
||||||
|
eid (:db/id snippet)
|
||||||
|
new-tags (get raw-patch :tags '[])
|
||||||
|
existing-tags (get snippet :snippet/tags '[])
|
||||||
|
tags-to-remove (vec (set/difference (set existing-tags) (set new-tags)))
|
||||||
|
retracts (map #(vector :db/retract eid :snippet/tags %) tags-to-remove)
|
||||||
|
patch (merge (to-update raw-patch) {:db/id eid})]
|
||||||
|
(t/log! {:level :info, :data {:patch patch :retracts retracts :slug slug :eid eid}} "Patching snippet")
|
||||||
|
(when (nil? eid)
|
||||||
|
(throw (ex-info "Snippet not found" {:slug slug})))
|
||||||
|
(when-not (m/validate update-schema patch)
|
||||||
|
(throw (ex-info "Invalid patch" {:errors (m/explain update-schema patch) :patch patch})))
|
||||||
|
(d/transact conn {:tx-data (into [patch] retracts)})))
|
||||||
|
|
||||||
|
(defn update-snippet [& args]
|
||||||
|
(let [res (apply patch-snippet-in-db args)]
|
||||||
|
(t/log! {:level :info, :data {:res res :args args}} "Finished patching snippet")))
|
||||||
|
|
||||||
|
(defn list-snippets-in-db
|
||||||
|
"List all the snippets"
|
||||||
|
[]
|
||||||
|
(let [conn (get-conn)
|
||||||
|
db (d/db conn)
|
||||||
|
query '[:find (pull ?e [*])
|
||||||
|
:where
|
||||||
|
[?e :snippet/slug]]]
|
||||||
|
(->> (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)
|
||||||
|
eid (:db/id (get-snippet-by-slug-from-db slug))]
|
||||||
|
(if (nil? eid)
|
||||||
|
nil
|
||||||
|
(d/transact conn {:tx-data [[:db/retractEntity eid]]}))))
|
||||||
|
|
||||||
|
(defn list-tags
|
||||||
|
"List all tags used in snippets with their counts."
|
||||||
|
[]
|
||||||
|
(let [conn (get-conn)
|
||||||
|
db (d/db conn)
|
||||||
|
query '[:find ?tag (count ?e)
|
||||||
|
:where
|
||||||
|
[?e :snippet/tags ?tag]]]
|
||||||
|
(d/q query db)))
|
||||||
|
|
||||||
|
(defn get-snippets-by-tag-in-db
|
||||||
|
"Get all snippets that have a specific tag."
|
||||||
|
[tag]
|
||||||
|
(let [conn (get-conn)
|
||||||
|
db (d/db conn)
|
||||||
|
query '[:find (pull ?e [*])
|
||||||
|
:in $ ?tag
|
||||||
|
:where
|
||||||
|
[?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))
|
||||||
|
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
(ns snippets.infra.db2
|
|
||||||
(:require
|
|
||||||
[clojure.set :as set]
|
|
||||||
[datomic.client.api :as d]
|
|
||||||
[malli.core :as m]
|
|
||||||
[snippets.infra.config :as config]
|
|
||||||
[taoensso.telemere :as t]))
|
|
||||||
|
|
||||||
;; 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 datomic-config (:datomic (config/get-config)))
|
|
||||||
|
|
||||||
(def client (d/client (merge {:server-type :datomic-local
|
|
||||||
:system "dev"} datomic-config)))
|
|
||||||
|
|
||||||
(def db-name "snippets")
|
|
||||||
|
|
||||||
;; Create the database if it doesn't exist
|
|
||||||
(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 []
|
|
||||||
(d/connect client {:db-name db-name}))
|
|
||||||
|
|
||||||
;; Define the schema for snippets
|
|
||||||
;; Transact this once to set up the database structure
|
|
||||||
(def snippet-schema
|
|
||||||
[{:db/ident :snippet/title
|
|
||||||
:db/valueType :db.type/string
|
|
||||||
:db/cardinality :db.cardinality/one}
|
|
||||||
{:db/ident :snippet/slug
|
|
||||||
:db/valueType :db.type/string
|
|
||||||
:db/cardinality :db.cardinality/one
|
|
||||||
:db/unique :db.unique/value}
|
|
||||||
{:db/ident :snippet/markdown
|
|
||||||
:db/valueType :db.type/string
|
|
||||||
:db/cardinality :db.cardinality/one}
|
|
||||||
{:db/ident :snippet/tags
|
|
||||||
:db/valueType :db.type/string
|
|
||||||
:db/cardinality :db.cardinality/many}
|
|
||||||
{:db/ident :snippet/pub-date
|
|
||||||
:db/valueType :db.type/instant
|
|
||||||
:db/cardinality :db.cardinality/one}])
|
|
||||||
|
|
||||||
(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
|
|
||||||
"Should be run at startup to ensure the database and schema are created."
|
|
||||||
[]
|
|
||||||
(ensure-db)
|
|
||||||
(ensure-schema))
|
|
||||||
|
|
||||||
(defn- snippet-to-entity
|
|
||||||
"Convert a snippet map to a Datomic DB entity."
|
|
||||||
[snippet]
|
|
||||||
{:snippet/title (:title snippet)
|
|
||||||
:snippet/slug (:slug snippet)
|
|
||||||
:snippet/markdown (:markdown snippet)
|
|
||||||
: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."
|
|
||||||
[:map
|
|
||||||
[:snippet/title :string]
|
|
||||||
[:snippet/slug :string]
|
|
||||||
[:snippet/markdown :string]
|
|
||||||
[:snippet/tags [:vector :string]]
|
|
||||||
[:snippet/pub-date [:fn #(instance? java.util.Date %)]]])
|
|
||||||
|
|
||||||
(defn- valid-create?
|
|
||||||
"Check if a snippet map is a valid Datomic entity."
|
|
||||||
[entity]
|
|
||||||
(m/validate create-schema entity))
|
|
||||||
|
|
||||||
(defn create-snippets
|
|
||||||
"Create new snippets in the database."
|
|
||||||
[snippets]
|
|
||||||
(t/log! {:level :info, :data {:slugs (map :slug snippets)}} "Saving new snippets to db")
|
|
||||||
(let [conn (get-conn)
|
|
||||||
entities (map snippet-to-entity snippets)]
|
|
||||||
(if (every? valid-create? entities)
|
|
||||||
(d/transact conn {:tx-data entities})
|
|
||||||
(throw (ex-info "Invalid snippet entity" {:entities entities})))))
|
|
||||||
|
|
||||||
;; read
|
|
||||||
(defn- get-snippet-by-slug-from-db
|
|
||||||
"Get a single snippet by its slug."
|
|
||||||
[slug]
|
|
||||||
(let [conn (get-conn)
|
|
||||||
db (d/db conn)
|
|
||||||
query '[:find (pull ?e [*])
|
|
||||||
:in $ ?slug
|
|
||||||
:where [?e :snippet/slug ?slug]]
|
|
||||||
snippet (ffirst (d/q query db slug))]
|
|
||||||
(t/log! {:level :info, :data {:slug slug :snippet snippet}} "Got snippet by slug")
|
|
||||||
snippet))
|
|
||||||
|
|
||||||
(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."
|
|
||||||
[:map
|
|
||||||
[:db/id :int]
|
|
||||||
[:snippet/title {:optional true} :string]
|
|
||||||
[:snippet/slug {:optional true} :string]
|
|
||||||
[:snippet/markdown {:optional true} :string]
|
|
||||||
[:snippet/tags {:optional true} [:vector :string]]])
|
|
||||||
|
|
||||||
(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-in-db
|
|
||||||
"Update specific fields of a snippet."
|
|
||||||
[slug raw-patch]
|
|
||||||
(let [conn (get-conn)
|
|
||||||
snippet (get-snippet-by-slug-from-db slug)
|
|
||||||
eid (:db/id snippet)
|
|
||||||
new-tags (get raw-patch :tags '[])
|
|
||||||
existing-tags (get snippet :snippet/tags '[])
|
|
||||||
tags-to-remove (vec (set/difference (set existing-tags) (set new-tags)))
|
|
||||||
retracts (map #(vector :db/retract eid :snippet/tags %) tags-to-remove)
|
|
||||||
patch (merge (to-update raw-patch) {:db/id eid})]
|
|
||||||
(t/log! {:level :info, :data {:patch patch :retracts retracts :slug slug :eid eid}} "Patching snippet")
|
|
||||||
(when (nil? eid)
|
|
||||||
(throw (ex-info "Snippet not found" {:slug slug})))
|
|
||||||
(when-not (m/validate update-schema patch)
|
|
||||||
(throw (ex-info "Invalid patch" {:errors (m/explain update-schema patch) :patch patch})))
|
|
||||||
(d/transact conn {:tx-data (into [patch] retracts)})))
|
|
||||||
|
|
||||||
(defn update-snippet [& args]
|
|
||||||
(let [res (apply patch-snippet-in-db args)]
|
|
||||||
(t/log! {:level :info, :data {:res res :args args}} "Finished patching snippet")))
|
|
||||||
|
|
||||||
(defn list-snippets-in-db
|
|
||||||
"List all the snippets"
|
|
||||||
[]
|
|
||||||
(let [conn (get-conn)
|
|
||||||
db (d/db conn)
|
|
||||||
query '[:find (pull ?e [*])
|
|
||||||
:where
|
|
||||||
[?e :snippet/slug]]]
|
|
||||||
(->> (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)
|
|
||||||
eid (:db/id (get-snippet-by-slug-from-db slug))]
|
|
||||||
(if (nil? eid)
|
|
||||||
nil
|
|
||||||
(d/transact conn {:tx-data [[:db/retractEntity eid]]}))))
|
|
||||||
|
|
||||||
(defn list-tags
|
|
||||||
"List all tags used in snippets with their counts."
|
|
||||||
[]
|
|
||||||
(let [conn (get-conn)
|
|
||||||
db (d/db conn)
|
|
||||||
query '[:find ?tag (count ?e)
|
|
||||||
:where
|
|
||||||
[?e :snippet/tags ?tag]]]
|
|
||||||
(d/q query db)))
|
|
||||||
|
|
||||||
(defn get-snippets-by-tag-in-db
|
|
||||||
"Get all snippets that have a specific tag."
|
|
||||||
[tag]
|
|
||||||
(let [conn (get-conn)
|
|
||||||
db (d/db conn)
|
|
||||||
query '[:find (pull ?e [*])
|
|
||||||
:in $ ?tag
|
|
||||||
:where
|
|
||||||
[?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))
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
(:require
|
(:require
|
||||||
[snippets.infra.api :as api]
|
[snippets.infra.api :as api]
|
||||||
[clojure.core.server]
|
[clojure.core.server]
|
||||||
[snippets.infra.db2 :refer [start-up-check]]
|
[snippets.infra.db :refer [start-up-check]]
|
||||||
[taoensso.telemere :as t])
|
[taoensso.telemere :as t])
|
||||||
(:gen-class))
|
(:gen-class))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
(ns snippets.use-cases.backfill-db2
|
|
||||||
(:require
|
|
||||||
[snippets.infra.db :as old-db]
|
|
||||||
[snippets.infra.db2 :as new-db]
|
|
||||||
[taoensso.telemere :as t]))
|
|
||||||
|
|
||||||
(defn- zdt-to-date [zdt]
|
|
||||||
(java.util.Date/from (.toInstant zdt)))
|
|
||||||
|
|
||||||
(defn backfill []
|
|
||||||
(t/log! {:level :info} "Backfilling DB2")
|
|
||||||
(let [old-snippets (old-db/list-snippets {})
|
|
||||||
new-snippets (map #(assoc % :pub-date (zdt-to-date (:pub-date %))) old-snippets)]
|
|
||||||
(t/log! {:level :info :data {:count (count new-snippets)}} "Creating snippets")
|
|
||||||
(new-db/create-snippets new-snippets)))
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
(ns snippets.use-cases.create
|
(ns snippets.use-cases.create
|
||||||
(:require
|
(:require
|
||||||
[taoensso.telemere :as t]
|
[taoensso.telemere :as t]
|
||||||
[snippets.infra.db2 :as db]))
|
[snippets.infra.db :as db]))
|
||||||
|
|
||||||
(defn create-snippet [{:keys [title slug markdown tags]}]
|
(defn create-snippet [{:keys [title slug markdown tags]}]
|
||||||
(let [pub-date (java.util.Date.)]
|
(let [pub-date (java.util.Date.)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(ns snippets.use-cases.delete
|
(ns snippets.use-cases.delete
|
||||||
(:require
|
(:require
|
||||||
[snippets.infra.db2 :as db]
|
[snippets.infra.db :as db]
|
||||||
[taoensso.telemere :as t]))
|
[taoensso.telemere :as t]))
|
||||||
|
|
||||||
(defn delete-snippet [slug]
|
(defn delete-snippet [slug]
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
(:require
|
(:require
|
||||||
[taoensso.telemere :as t]
|
[taoensso.telemere :as t]
|
||||||
[malli.core :as m]
|
[malli.core :as m]
|
||||||
[snippets.infra.db2 :as db]))
|
[snippets.infra.db :as db]))
|
||||||
|
|
||||||
(def valid-patch?
|
(def valid-patch?
|
||||||
(m/validator
|
(m/validator
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
(ns snippets.use-cases.view
|
(ns snippets.use-cases.view
|
||||||
(:require
|
(:require
|
||||||
[taoensso.telemere :as t]
|
[taoensso.telemere :as t]
|
||||||
[snippets.infra.db2 :as db]))
|
[snippets.infra.db :as db]))
|
||||||
|
|
||||||
(defn serialize-snippet
|
(defn serialize-snippet
|
||||||
"Converts snippet pub-date to ISO-8601 string for EDN serialization"
|
"Converts snippet pub-date to ISO-8601 string for EDN serialization"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue