git.fiddlerwoaroof.com
Browse code

(init)

Ed Langley authored on 29/08/2019 00:43:13
Showing 8 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,2 @@
1
+*.fasl
2
+*~
0 3
new file mode 100644
... ...
@@ -0,0 +1,23 @@
1
+(in-package :fwoar.lack.cors.middleware)
2
+;; cors middleware
3
+(defparameter *acao-lens*
4
+  (data-lens:<>1 (data-lens.lenses:make-list-lens 1)
5
+                 (data-lens.lenses:make-plist-lens :Access-Control-Allow-Origin)))
6
+(defparameter *acah-lens*
7
+  (data-lens:<>1 (data-lens.lenses:make-list-lens 1)
8
+                 (data-lens.lenses:make-plist-lens :Access-Control-Allow-Headers)))
9
+(defparameter *acam-lens*
10
+  (data-lens:<>1 (data-lens.lenses:make-list-lens 1)
11
+                 (data-lens.lenses:make-plist-lens :Access-Control-Allow-Methods)))
12
+
13
+(defun cors-middleware (app)
14
+  (lambda (env)
15
+    (rutilsx.threading:->>
16
+     (if (eq :options
17
+             (getf env :request-method))
18
+         '(200 nil nil)
19
+         (let ((res (funcall app env)))
20
+           res))
21
+     (data-lens.lenses:set *acao-lens* "*")
22
+     (data-lens.lenses:set *acah-lens* "Content-Type")
23
+     (data-lens.lenses:set *acam-lens* "GET,POST,DELETE,PATCH"))))
0 24
new file mode 100644
... ...
@@ -0,0 +1,36 @@
1
+(in-package :fwoar.lack.json.middleware)
2
+
3
+;; json middleware
4
+(defparameter *result-lens*
5
+  (data-lens.lenses:make-list-lens 2))
6
+
7
+(defun json-middleware (app)
8
+  (lambda (env)
9
+    (let ((res (funcall app env)))
10
+      (data-lens.lenses:over *result-lens* 'encode-result
11
+                             res))))
12
+
13
+(defun encode-json-to-string (v)
14
+  (yason:with-output-to-string* (:indent t)
15
+    (yason:encode v
16
+                  yason::*json-output*)))
17
+
18
+(defclass json-result ()
19
+  ((%v :initarg :v :reader json-value)))
20
+
21
+(defun wrap-result (v)
22
+  (make-instance 'json-result :v v))
23
+
24
+(defgeneric encode-result (v)
25
+  (:method (v)
26
+    v)
27
+  (:method ((v json-result))
28
+    (list (encode-json-to-string (json-value v)))))
29
+
30
+(defmethod yason:encode ((o fset:seq) &optional s)
31
+  (yason:encode (coerce (fset:convert 'list o) 'vector)
32
+                s))
33
+
34
+(defmethod yason:encode ((o fset:map) &optional s)
35
+  (yason:encode (fset:convert 'hash-table o)
36
+                s))
0 37
new file mode 100644
... ...
@@ -0,0 +1,35 @@
1
+(in-package :fwoar.todo)
2
+
3
+;;; entrypoint
4
+(defun setup ()
5
+  (let ((app (make-instance 'ningle:<app>)))
6
+    (prog1 app (setup-routes app))))
7
+
8
+(defvar *handler*)
9
+
10
+(defun is-running ()
11
+  (and (boundp '*handler*)
12
+       *handler*))
13
+
14
+(defun ensure-started (&rest r &key port)
15
+  (declare (ignore port))
16
+  (let ((app (setup)))
17
+    (values app
18
+            (setf *handler*
19
+                  (if (not (is-running))
20
+                      (apply 'clack:clackup
21
+                             (lack.builder:builder
22
+                              :accesslog
23
+                              'fwoar.lack.cors.middleware:cors-middleware
24
+                              'fwoar.lack.json.middleware:json-middleware
25
+                              app)
26
+                             r)
27
+                      *handler*)))))
28
+
29
+(defun stop ()
30
+  (if (is-running)
31
+      (progn
32
+        (clack:stop *handler*)
33
+        (makunbound '*handler*)
34
+        nil)
35
+      nil))
0 36
new file mode 100644
... ...
@@ -0,0 +1,50 @@
1
+(in-package :fwoar.todo)
2
+
3
+;; todo "database" api
4
+(defvar *todos* (fset:empty-map))
5
+
6
+(defun todos ()
7
+  (gmap:gmap :seq
8
+             (lambda (_ b)
9
+               (declare (ignore _))
10
+               b)
11
+             (:map *todos*)))
12
+
13
+(defun todo (id)
14
+  (let ((todo (fset:@ *todos* id)))
15
+    todo))
16
+
17
+(defun (setf todo) (new-value id)
18
+  (setf *todos*
19
+        (fset:with *todos* id new-value))
20
+  new-value)
21
+
22
+(defun delete-todo (id)
23
+  (setf *todos*
24
+        (fset:less *todos* id)))
25
+
26
+(defparameter *cur-id* 0)
27
+(defun next-id ()
28
+  (incf *cur-id*))
29
+
30
+(defun new-todo (value)
31
+  (let ((id (next-id)))
32
+    (setf (todo id)
33
+          (alexandria:alist-hash-table
34
+           (rutilsx.threading:->>
35
+            value
36
+            (acons "completed" 'yason:false)
37
+            (acons "url" (format nil "http://localhost:5000/todo/~d" id)))
38
+           :test 'equal))))
39
+
40
+(defun clear-todos ()
41
+  (setf *todos*
42
+        (fset:empty-map)))
43
+
44
+(defun update-todo (id v)
45
+  (setf (todo id)
46
+        (serapeum:merge-tables (or (todo id)
47
+                                   (make-hash-table :test 'equal))
48
+                               (alexandria:alist-hash-table
49
+                                v
50
+                                :test 'equal))))
0 51
new file mode 100644
... ...
@@ -0,0 +1,11 @@
1
+(defpackage :fwoar.lack.json.middleware
2
+  (:use :cl)
3
+  (:export #:json-middleware))
4
+
5
+(defpackage :fwoar.lack.cors.middleware
6
+  (:use :cl)
7
+  (:export #:cors-middleware))
8
+
9
+(defpackage :fwoar.todo
10
+  (:use :cl)
11
+  (:export ))
0 12
new file mode 100644
... ...
@@ -0,0 +1,38 @@
1
+(in-package :fwoar.todo)
2
+
3
+(defmacro defroutes (app &body routes)
4
+  "Define a set of routes for given paths. the ROUTES parameter expects this format:
5
+   ((\"/path/to/{route}\" :method :POST) route-callback) the AS-ROUTE macro helps one
6
+   avoid binding function values to the route for flexibility."
7
+  (alexandria:once-only (app)
8
+    `(progn
9
+       ,@(loop for ((target &key method) callback) in routes
10
+               collect `(setf (ningle:route ,app ,target :method ,(or method :GET)) ,callback)))))
11
+
12
+
13
+;; routing
14
+(defun success (value)
15
+  (list 200 nil value))
16
+
17
+(defmacro handler ((&optional (sym (gensym "PARAMS"))) &body body)
18
+  `(lambda (,sym)
19
+     (declare (ignorable ,sym))
20
+     (success
21
+      (fwoar.lack.json.middleware::wrap-result
22
+       (progn ,@body)))))
23
+
24
+(defun get-id (params)
25
+  (parse-integer (serapeum:assocdr :id params)))
26
+
27
+(defun setup-routes (app)
28
+  (defroutes app
29
+    (("/" :method :GET)            (handler () (todos)))
30
+    (("/" :method :POST)           (handler (v) (new-todo v)))
31
+    (("/" :method :DELETE)         (handler () (clear-todos)))
32
+    (("/todo/:id" :method :GET)    (handler (v) (todo (get-id v))))
33
+    (("/todo/:id" :method :DELETE) (handler (v)
34
+                                     (delete-todo (get-id v))
35
+                                     nil))
36
+    (("/todo/:id" :method :PATCH)  (handler (v)
37
+                                     (update-todo (get-id v) 
38
+                                                  (remove :id v :key #'car))))))
0 39
new file mode 100644
... ...
@@ -0,0 +1,25 @@
1
+;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; Package: ASDF-USER -*-
2
+(in-package :asdf-user)
3
+
4
+(defsystem :todo-backend 
5
+  :description ""
6
+  :author "Ed L <edward@elangley.org>"
7
+  :license "MIT"
8
+  :depends-on (#:alexandria
9
+               #:uiop
10
+               #:serapeum
11
+               #:yason
12
+               #:fset
13
+               #:ningle
14
+               #:alexandria
15
+               #:clack)
16
+  :components ((:file "package")
17
+               (:file "lack-cors-middleware" :depends-on ("package"))
18
+               (:file "lack-json-middleware" :depends-on ("package"))
19
+               (:file "model" :depends-on ("package"))
20
+               (:file "routing" :depends-on ("package"
21
+                                             "model"))
22
+               (:file "main" :depends-on ("package"
23
+                                          "routing"
24
+                                          "lack-cors-middleware"
25
+                                          "lack-json-middleware"))))