-
Notifications
You must be signed in to change notification settings - Fork 0
/
core.clj
99 lines (87 loc) · 3.71 KB
/
core.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
(ns api.core
(:require [clojure.spec.alpha :as s]
[reitit.ring :as r]
[reitit.ring.coercion :as rrc]
[reitit.coercion.spec]
[muuntaja.middleware :as mw]
[ring.middleware.cors :refer [wrap-cors]]
[ring.adapter.jetty :as j])
(:gen-class))
(s/def :task/uri (s/and string? not-empty))
(s/def :task/title (s/and string? not-empty))
(s/def ::task (s/keys :req [:task/uri :task/title]))
(s/def :event/name (s/and string? not-empty))
(s/def ::event (s/keys :req [:event/name]))
(s/def :app/task-event (s/merge ::event ::task))
(s/def :error/reason (s/and string? not-empty))
(s/def :error/fault (s/and string? not-empty))
(s/def :error/details any?)
(s/def :app/system-error (s/keys :req [:error/reason :error/fault :error/details]))
;;; --------------------------- state reducer --------------------------------
(defn apply-event
"Reduces a valid application state from a vector of task events. The
events must be sorted in order of when the events were received."
[tasks event]
(case (:event/name event)
"task-added"
(->> (select-keys event [:task/uri :task/title])
(conj (remove #(= (:task/uri event) (:task/uri %)) tasks)))
"task-completed"
(remove #(= (:task/uri %) (:task/uri event)) tasks)
;;; else
(throw (ex-info "cannot apply event" event))))
;;; --------------------------- HTTP server ----------------------------------
(defn handle-task-event
"Sends the received event to the event-store and responds to the request."
[event-name send-to-store]
(fn [request]
(let [event {:event/name event-name
:task/uri (get-in request [:body-params :uri])
:task/title (get-in request [:body-params :title])}]
(when (s/valid? :app/task-event event) (send-to-store event)))
{:status 201}))
(defn tasks-snapshot-from
"Returns a snapshot of tasks from the history of task events. Only responds
when the snapshot was created successfully, otherwise returns an error."
[events-from-store]
(fn [_]
(try
{:status 200 :body (reduce apply-event [] (events-from-store))}
(catch clojure.lang.ExceptionInfo _ {:status 500}))))
(defn app
"Listens to HTTP requests and routes them to the correct handlers.
`event-store` is a map of:
- a :send function. It receives an event (map with at least :event/name
and any other keywords related to the event).
- an :events function. It responds with the list of events in the store."
[event-store]
(let [{send-to-store :send stored-events :events} event-store]
(r/ring-handler
(r/router
[["/tasks/added"
{:post (handle-task-event "task-added" send-to-store)
:parameters {:body {:uri :task/uri :title :task/title}}}]
["/tasks/completed"
{:post (handle-task-event "task-completed" send-to-store)
:parameters {:body {:uri :task/uri :title :task/title}}}]
["/tasks" {:get (tasks-snapshot-from stored-events)}]]
{:data
{:coercion reitit.coercion.spec/coercion
:middleware [mw/wrap-format
rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]}})
(r/create-default-handler))))
(defn in-memory-event-store []
(let [events (atom [])]
{:send (fn [event]
(when (s/valid? ::event event)
(swap! events conj event)))
:events (fn [] @events)}))
(defn -main [& args]
(j/run-jetty
(-> (app (in-memory-event-store))
(wrap-cors :access-control-allow-credentials "true"
:access-control-allow-origin [#".*"]
:access-control-allow-methods [:get :post]))
{:port 3000}))