Skip to content

Commit 2b093b7

Browse files
committed
Lesson 08 has been added
1 parent 5c8e3e0 commit 2b093b7

26 files changed

+908
-0
lines changed

api-example/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Minimal API example with some tests
2+
3+
## Run
4+
5+
```
6+
docker compose up -d
7+
```
8+
9+
```
10+
lein run local
11+
```

api-example/config.edn

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{:core/db
2+
{:config #profile {:local {:dbtype "postgres"
3+
:dbname "api_example_db"
4+
:host "localhost"
5+
:port 5432
6+
:user "postgres"
7+
:password "123"}
8+
:test {:dbtype "postgres"
9+
:dbname "api_example_db_test"
10+
:host "localhost"
11+
:port 5433
12+
:user "postgres"
13+
:password "123"}}}
14+
15+
:core/migrator
16+
{:db #ig/ref :core/db}
17+
18+
:core/app
19+
{:ds #ig/ref :core/migrator}
20+
21+
:core/server
22+
{:app #ig/ref :core/app
23+
:config #profile {:local {:host "127.0.0.1"
24+
:port 5555
25+
:join? false}
26+
:test {:host "127.0.0.1"
27+
:port 5556
28+
:join? false}}}}

api-example/docker-compose.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: "3.8"
2+
3+
services:
4+
db:
5+
image: postgres
6+
container_name: api_example_db
7+
environment:
8+
POSTGRES_DB: api_example_db
9+
POSTGRES_USER: postgres
10+
POSTGRES_PASSWORD: "123"
11+
ports:
12+
- 5432:5432
13+
volumes:
14+
- api_example_db_volume:/var/lib/postgresql/data
15+
16+
db_test:
17+
image: postgres
18+
container_name: api_example_db_test
19+
environment:
20+
POSTGRES_DB: api_example_db_test
21+
POSTGRES_USER: postgres
22+
POSTGRES_PASSWORD: "123"
23+
ports:
24+
- 5433:5432
25+
26+
volumes:
27+
api_example_db_volume:

api-example/project.clj

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
(defproject api-example "0.1.0-SNAPSHOT"
2+
:description "FIXME: write description"
3+
:url "http://example.com/FIXME"
4+
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
5+
:url "https://www.eclipse.org/legal/epl-2.0/"}
6+
:dependencies [[aero "1.1.6"]
7+
[camel-snake-kebab "0.4.3"]
8+
[com.github.seancorfield/honeysql "2.4.1033"]
9+
[com.github.seancorfield/next.jdbc "1.3.874"]
10+
[compojure "1.7.0"]
11+
[integrant "0.8.0"]
12+
[migratus "1.4.9"]
13+
[nubank/matcher-combinators "3.8.5"]
14+
[org.clojure/clojure "1.11.1"]
15+
[org.postgresql/postgresql "42.6.0"]
16+
[org.slf4j/slf4j-log4j12 "2.0.7"] ; Required by migratus.
17+
[ring "1.10.0"]
18+
[ring/ring-json "0.5.1"]
19+
[ring/ring-mock "0.4.0"]]
20+
:main ^:skip-aot api-example.core
21+
:target-path "target/%s"
22+
:profiles {:uberjar {:aot :all
23+
:jvm-opts ["-Dclojure.compiler.direct-linking=true"]}}
24+
25+
:test-selectors {:integration :integration
26+
:unit (complement :integration)
27+
:crud (fn [m]
28+
(->> (:name m)
29+
(name)
30+
(re-find #"get|create|delete|update|replace")))})
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
CREATE TABLE "account" (
2+
"id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
3+
"username" VARCHAR (255) UNIQUE NOT NULL,
4+
"password" VARCHAR (255) NOT NULL,
5+
"email" VARCHAR (255) UNIQUE NOT NULL,
6+
"created_on" TIMESTAMP NOT NULL
7+
);
8+
--;;
9+
INSERT INTO "account"(
10+
"username",
11+
"password",
12+
"email",
13+
"created_on"
14+
)
15+
VALUES (
16+
'admin',
17+
'password123',
18+
19+
NOW()
20+
),
21+
(
22+
'nikita',
23+
'password',
24+
25+
NOW()
26+
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TABLE "role"(
2+
"id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
3+
"name" VARCHAR (255) UNIQUE NOT NULL
4+
);
5+
--;;
6+
INSERT INTO "role"("name")
7+
VALUES ('admin'),
8+
('user');
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
CREATE TABLE "account_role" (
2+
"account_id" INT NOT NULL,
3+
"role_id" INT NOT NULL,
4+
"grant_date" TIMESTAMP,
5+
PRIMARY KEY ("account_id", "role_id"),
6+
FOREIGN KEY ("role_id") REFERENCES "role" ("id"),
7+
FOREIGN KEY ("account_id") REFERENCES "account" ("id")
8+
);
9+
--;;
10+
INSERT INTO "account_role"("account_id", "role_id", "grant_date")
11+
VALUES (
12+
(
13+
SELECT "id"
14+
FROM "account"
15+
WHERE "username" = 'admin'
16+
),
17+
(
18+
SELECT "id"
19+
FROM "role"
20+
WHERE "name" = 'admin'
21+
),
22+
NOW()
23+
),
24+
(
25+
(
26+
SELECT "id"
27+
FROM "account"
28+
WHERE "username" = 'nikita'
29+
),
30+
(
31+
SELECT "id"
32+
FROM "role"
33+
WHERE "name" = 'user'
34+
),
35+
NOW()
36+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(ns api-example.api.account
2+
(:require [compojure.core :as c]
3+
[compojure.coercions :refer [as-int]]
4+
[api-example.service.account :as service.account]))
5+
6+
(def routes
7+
(c/context "/accounts" {:keys [ctx]}
8+
(c/GET "/" [] (service.account/get-all ctx))
9+
(c/POST "/" {:keys [body]} (service.account/create ctx body))
10+
(c/context "/:id" [id :<< as-int]
11+
(c/GET "/" [] (service.account/get-by-id ctx id)))))

api-example/src/api_example/core.clj

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
(ns api-example.core
2+
(:require [camel-snake-kebab.core :as csk]
3+
[clojure.spec.alpha :as s]
4+
[integrant.core :as ig]
5+
[api-example.middleware]
6+
[api-example.router :refer [router]]
7+
[api-example.spec :as spec]
8+
[api-example.util :as u]
9+
[migratus.core :as migratus]
10+
[next.jdbc :as jdbc]
11+
[ring.adapter.jetty :refer [run-jetty]]
12+
[ring.middleware.json])
13+
(:gen-class))
14+
15+
;; DB
16+
17+
(defmethod ig/pre-init-spec :core/db [_]
18+
(s/keys :req-un [::spec/config]))
19+
20+
(defmethod ig/init-key :core/db
21+
[_ {:keys [config]}]
22+
config)
23+
24+
;; Migrator
25+
26+
(defmethod ig/init-key :core/migrator
27+
[_ {:keys [db]}]
28+
(let [ds (jdbc/get-datasource db)
29+
config {:store :database
30+
:migration-dir "migrations"
31+
:db {:datasource ds}}]
32+
(migratus/migrate config)
33+
ds))
34+
35+
(comment
36+
(migratus/create {:migration-dir "migrations"} "create-account-role")
37+
(migratus/create {:migration-dir "migrations"} "create-account" :edn)
38+
)
39+
40+
;; App
41+
42+
(def ^:dynamic *ctx*
43+
nil)
44+
45+
(defmethod ig/init-key :core/app
46+
[_ {:keys [ds]}]
47+
(binding [*ctx* {:ds ds}]
48+
(-> router
49+
50+
(ring.middleware.json/wrap-json-body {:key-fn csk/->kebab-case-keyword})
51+
52+
(api-example.middleware/wrap-request-ctx *ctx*)
53+
54+
(api-example.middleware/wrap-postgres-exception)
55+
(api-example.middleware/wrap-fallback-exception)
56+
57+
(ring.middleware.json/wrap-json-response {:key-fn csk/->camelCaseString}))))
58+
59+
;; Server
60+
61+
(defmethod ig/init-key :core/server
62+
[_ {:keys [app config]}]
63+
(run-jetty app config))
64+
65+
(defmethod ig/halt-key! :core/server
66+
[_ server]
67+
(.stop server))
68+
69+
;; Whole system
70+
71+
(defonce system
72+
(atom nil))
73+
74+
(defn start-system
75+
[config]
76+
(when @system
77+
(ig/halt! @system))
78+
(reset! system (ig/init config)))
79+
80+
(defn -main
81+
[profile & _args]
82+
(let [profile-kw (keyword profile)
83+
config (u/load-config "config.edn" {:profile profile-kw})]
84+
(println config)
85+
(start-system config)))
86+
87+
#_(-main "local")
88+
#_(ig/halt! @system)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
(ns api-example.dao.account
2+
(:require [api-example.dao.util :refer [sql-format execute! execute-one!]]))
3+
4+
(def table
5+
:account)
6+
7+
(defn get-all
8+
[ds]
9+
(let [query (sql-format {:select [:*]
10+
:from table})]
11+
(execute! ds query)))
12+
13+
(defn get-by-id
14+
[ds id]
15+
(let [query (sql-format {:select [:*]
16+
:from table
17+
:where [:= :id id]})]
18+
(execute-one! ds query)))
19+
20+
(defn create
21+
[ds entity]
22+
(let [query (sql-format {:insert-into table
23+
:values [entity]})]
24+
(execute-one! ds query)))

0 commit comments

Comments
 (0)