diff --git a/README.md b/README.md index f6bbde3..c582409 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Code Snippets -App to store my code snippets. +Backend application to store my code snippets and make them available via REST API. + +[CLI CMS Companion Project](https://git.sr.ht/~travisshears/code-snippets-cli-cms) + +This project is written in [Clojure](https://clojure.org/) and data is stored in [XTDB](https://xtdb.dev/). ## Links @@ -16,3 +20,12 @@ App to store my code snippets. ``` $ clojure -M -m snippets.infra.api ``` + +### How to create docker image + +At this point I'm using a private AWS ECR repository for the project and deploying it in my homelab. + +```shell +$ export AWS_PROFILE=personal +$ docker buildx build --platform linux/amd64,linux/arm64 -t 853019563312.dkr.ecr.eu-central-1.amazonaws.com/snippets-homelabstack:latest --push . +``` diff --git a/bruno/CodeSnippets/delete_snippet.bru b/bruno/CodeSnippets/delete_snippet.bru index 07ef77e..8e9a782 100644 --- a/bruno/CodeSnippets/delete_snippet.bru +++ b/bruno/CodeSnippets/delete_snippet.bru @@ -5,13 +5,13 @@ meta { } delete { - url: {{host}}/api/snippet?id=90d00a80-78c4-4bc9-a066-01c988599d05 + url: {{host}}/api/snippet?id=d77d3463-c76e-4c53-a1d5-ecaf16c6c54e body: none auth: none } params:query { - id: 90d00a80-78c4-4bc9-a066-01c988599d05 + id: d77d3463-c76e-4c53-a1d5-ecaf16c6c54e } body:json { diff --git a/bruno/CodeSnippets/edit_snippet.bru b/bruno/CodeSnippets/edit_snippet.bru index fa6d897..dbfd39d 100644 --- a/bruno/CodeSnippets/edit_snippet.bru +++ b/bruno/CodeSnippets/edit_snippet.bru @@ -16,7 +16,7 @@ params:query { body:json { { - "title": "Updated from Bruno", - "tags": ["code", "mock"] + "title": "quick way to push last jj commit to git", + "tags": ["jj", "git"] } } diff --git a/bruno/CodeSnippets/get_snippet_by_slug.bru b/bruno/CodeSnippets/get_snippet_by_slug.bru new file mode 100644 index 0000000..3c17768 --- /dev/null +++ b/bruno/CodeSnippets/get_snippet_by_slug.bru @@ -0,0 +1,23 @@ +meta { + name: get_snippet_by_slug + type: http + seq: 10 +} + +get { + url: {{host}}/api/snippet-by-slug?slug=netcat-over-ping + body: none + auth: none +} + +params:query { + slug: netcat-over-ping +} + +body:json { + { + "title": "Test Snippet", + "markdown": "## Cool Snippet\ndoes a cool thing", + "tags": ["git", "jj"] + } +} diff --git a/bruno/CodeSnippets/get_snippets.bru b/bruno/CodeSnippets/get_snippets.bru index 8bf73c4..e2de66e 100644 --- a/bruno/CodeSnippets/get_snippets.bru +++ b/bruno/CodeSnippets/get_snippets.bru @@ -5,13 +5,13 @@ meta { } get { - url: {{host}}/api/snippets?limit=100&skip=0 + url: {{host}}/api/snippets?limit=25&skip=0 body: none auth: none } params:query { - limit: 100 + limit: 25 skip: 0 } diff --git a/bruno/CodeSnippets/get_tag.bru b/bruno/CodeSnippets/get_tag.bru new file mode 100644 index 0000000..c040ed9 --- /dev/null +++ b/bruno/CodeSnippets/get_tag.bru @@ -0,0 +1,23 @@ +meta { + name: get_tag + type: http + seq: 9 +} + +get { + url: {{host}}/api/tag?tag=git + body: none + auth: none +} + +params:query { + tag: git +} + +body:json { + { + "title": "Test Snippet", + "markdown": "## Cool Snippet\ndoes a cool thing", + "tags": ["git", "jj"] + } +} diff --git a/bruno/CodeSnippets/get_tags.bru b/bruno/CodeSnippets/get_tags.bru new file mode 100644 index 0000000..a8a876b --- /dev/null +++ b/bruno/CodeSnippets/get_tags.bru @@ -0,0 +1,19 @@ +meta { + name: get_tags + type: http + seq: 8 +} + +get { + url: {{host}}/api/tags + body: none + auth: none +} + +body:json { + { + "title": "Test Snippet", + "markdown": "## Cool Snippet\ndoes a cool thing", + "tags": ["git", "jj"] + } +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..71da131 --- /dev/null +++ b/build.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e + +export AWS_PROFILE=personal +export AWS_REGION=eu-central-1 + +REPO_NAME="snippets-homelabstack" + +if ! aws ecr describe-repositories --repository-names "$REPO_NAME" >/dev/null 2>&1; then + aws ecr create-repository --repository-name "$REPO_NAME" +fi + +docker buildx build --platform linux/amd64,linux/arm64 -t "853019563312.dkr.ecr.eu-central-1.amazonaws.com/${REPO_NAME}:latest" --push . + +echo "Docker image built and pushed to AWS ECR" diff --git a/deps.edn b/deps.edn index 6b12105..fbc464d 100644 --- a/deps.edn +++ b/deps.edn @@ -1,7 +1,7 @@ {:paths ["src"] - :deps {ring/ring-core {:mvn/version "1.13.0"} + :deps {;; api + ring/ring-core {:mvn/version "1.13.0"} ring/ring-jetty-adapter {:mvn/version "1.13.0"} - ;; logging, required by jetty: org.slf4j/slf4j-simple {:mvn/version "2.0.16"} ;; db diff --git a/src/snippets/infra/api.clj b/src/snippets/infra/api.clj index 1e96047..5f2c6fe 100644 --- a/src/snippets/infra/api.clj +++ b/src/snippets/infra/api.clj @@ -47,6 +47,26 @@ {:status 200 :body (format "Deleted snippet with id: %s if it existed" id)})) +(defn handle-view-tags [_args] + (let [tags (snippets.use-cases.view/view-tags)] + {:status 200 + :body tags})) + +(defn handle-view-snippets-by-tag [{params :query-params}] + (let [tag (get params "tag")] + {:status 200 + :body (snippets.use-cases.view/view-snippets-by-tag tag)})) + +(defn handle-view-tags [_] + (let [tags (snippets.use-cases.view/view-tags)] + {:status 200 + :body tags})) + +(defn handle-view-snippet-by-slug [{params :query-params}] + (let [slug (get params "slug")] + {:status 200 + :body (snippets.use-cases.view/view-snippet-by-slug slug)})) + (defn wrap [handler id] (fn [request] (update (handler request) :wrap (fnil conj '()) id))) @@ -58,6 +78,9 @@ mm/wrap-format [wrap :api]]} ["/ping" {:get handle-ping}] + ["/tags" {:get handle-view-tags}] + ["/tag" {:get handle-view-snippets-by-tag}] + ["/snippet-by-slug" {:get handle-view-snippet-by-slug}] ["/snippets" {:get handle-view-snippets}] ["/snippet" {:post handle-create-snippet :get handle-view-snippet diff --git a/src/snippets/infra/db.clj b/src/snippets/infra/db.clj index d35eed5..8346ef5 100644 --- a/src/snippets/infra/db.clj +++ b/src/snippets/infra/db.clj @@ -22,7 +22,10 @@ (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] - (first (xt/q client ['#(from :snippets [{:xt/id %} slug title {:xt/id id} markdown pub-date]) snippet-id]))) + (first (xt/q client ['#(from :snippets [{:xt/id %} slug title tags {:xt/id id} markdown pub-date]) snippet-id]))) + +(defn get-snippet-by-slug [slug] + (first (xt/q client ['#(from :snippets [{:xt/id id} {:slug %} slug title tags markdown pub-date]) slug]))) (defn put-snippet [id snippet] (t/log! {:level :info, :data {:snippet snippet :id id}} "Saving new snippet to db") @@ -39,3 +42,12 @@ (defn patch-snippet [id patch] (t/log! {:level :info, :data {:patch patch :id id}} "Patching snippet") (xt/execute-tx client [[:patch-docs :snippets (merge {:xt/id id} patch)]])) + +(defn list-tags [] + ;; (xt/q client '(-> (from :snippets [{:xt/id id} tags]) (unnest {:tag tags}) (without :tags) (aggregate tag {:ids (array-agg id)})))) + (xt/q client '(-> (from :snippets [{:xt/id id} tags]) (unnest {:tag tags}) (without :tags) (aggregate tag {:count (count id)})))) + +(defn get-snippets-by-tag [tag] + (map #(dissoc % :tag) + (xt/q client (eval (read-string + (format "(quote (-> (from :snippets [title slug tags pub-date]) (unnest {:tag tags}) (without :tags) (where (= tag \"%s\"))))" tag)))))) diff --git a/src/snippets/main.clj b/src/snippets/main.clj index d6fef1e..4e0e10d 100644 --- a/src/snippets/main.clj +++ b/src/snippets/main.clj @@ -1,5 +1,5 @@ (ns snippets.main - (:require [snippets.api :as api]) + (:require [snippets.infra.api :as api]) (:gen-class)) (defn -main [] diff --git a/src/snippets/use_cases/edit.clj b/src/snippets/use_cases/edit.clj index 5db3816..8e9ac2b 100644 --- a/src/snippets/use_cases/edit.clj +++ b/src/snippets/use_cases/edit.clj @@ -9,7 +9,7 @@ [:map {:closed true} [:markdown {:optional true} :string] [:title {:optional true} :string] - [:tags [:seqable :string]] + [:tags {:optional true} [:seqable :string]] [:slug {:optional true} :string]])) (defn edit-snippet [id patch] diff --git a/src/snippets/use_cases/view.clj b/src/snippets/use_cases/view.clj index ac6e83b..4edbacd 100644 --- a/src/snippets/use_cases/view.clj +++ b/src/snippets/use_cases/view.clj @@ -9,3 +9,15 @@ (defn view-snippets [& args] (db/list-snippets args)) + +(defn view-tags [] + (t/log! {:level :info} "Viewing tags") + (db/list-tags)) + +(defn view-snippets-by-tag [tag] + (t/log! {:level :info :data {:tag tag}} "Viewing snippet by tag") + (db/get-snippets-by-tag tag)) + +(defn view-snippet-by-slug [slug] + (t/log! {:level :info :data {:slug slug}} "Viewing snippet by slug") + (db/get-snippet-by-slug slug))