diff --git a/rails-to-caveman.asd b/rails-to-caveman.asd
index 731d45f..c1e3b1c 100644
--- a/rails-to-caveman.asd
+++ b/rails-to-caveman.asd
@@ -9,6 +9,7 @@
#:envy
#:cl-ppcre
#:uiop
+ #:local-time ; <-- Added Chapter 6
;; for @route annotation
#:cl-syntax-annot
diff --git a/src/model.lisp b/src/model.lisp
index ae38f84..33cc82e 100644
--- a/src/model.lisp
+++ b/src/model.lisp
@@ -1,5 +1,5 @@
-;;(in-package #:cl-user) ; Not sure if this needs to exist (Chapter 4)
-(defpackage rails-to-caveman.model
+(in-package #:cl-user) ; Not sure if this needs to exist (Chapter 4)
+(defpackage #:rails-to-caveman.model
(:use #:cl
#:rails-to-caveman.db
#:mito))
@@ -105,3 +105,7 @@ this tutorial was translated/ported from."
(mapcar #'mito:object-id
(mito:retrieve-dao 'rails-to-caveman.model::user))))
+;; A common strategy to deal with SQLite Boolean types is to set
+;; constants and refer to them instead of passing hard coded 1 & 0.
+(defconstant +false+ 0)
+(defconstant +true+ 1)
diff --git a/src/web.lisp b/src/web.lisp
index b435482..fc73d13 100644
--- a/src/web.lisp
+++ b/src/web.lisp
@@ -1,5 +1,5 @@
(in-package #:cl-user)
-(defpackage rails-to-caveman.web
+(defpackage #:rails-to-caveman.web
(:use #:cl
#:caveman2
#:rails-to-caveman.config
@@ -39,7 +39,10 @@
(render "users/index.html"
`(:users ,(with-connection (db)
(mito:select-dao 'rails-to-caveman.model::user
- (sxql:order-by :number))))))
+ (sxql:order-by :number)))
+ :token ,(token)
+ :notice (flash-gethash
+ :notice ningle:*request*))))
(defroute "/users/search" (&key |q|)
(render "users/index.html"
@@ -51,11 +54,207 @@
(sxql:order-by :number))))))
(defroute "/users/:id" (&key id)
- (setf id (parse-integer id))
- (render "users/show.html"
- `(:user ,(with-connection (db)
- (mito:find-dao 'rails-to-caveman.model::user
- :id id)))))
+ (let ((id (ignore-errors (parse-integer id))))
+ (if (null id)
+ (myway.mapper:next-route)
+ (let ((user (with-connection (db)
+ (mito:find-dao 'rails-to-caveman.model::user
+ :id id))))
+ ;; (setf id (parse-integer id))
+ (if user
+ (render "users/show.html"
+ `(:user ,(with-connection (db)
+ (mito:find-dao 'rails-to-caveman.model::user
+ :id id))
+ :notice ,(flash-gethash
+ :notice ningle:*session*)))
+ (on-exception *web* 404))))))
+
+(defroute "/user/new" ()
+ (render #P"users/new.html"
+ `(:users ,(with-connection (db)
+ (make-instance 'rails-to-caveman.model::user))
+ :token ,(token))))
+
+(defroute "/users/:id/edit" (&key id)
+ (let* ((id (ignore-errors (parse-integer id)))
+ (user (with-connection (db) ; NOTE `USER' AND NOT USERS.
+ (and id
+ (mito:find-dao 'rails-to-caveman.model::user
+ :id id)))))
+ (if user (render "users/edit.html"
+ `(:user ,user :token ,(token)))
+ (on-exception *web* 404))))
+
+(defroute ("/user/:id" :method :post)
+ (&key |authenticity-token|
+ id
+ (|number| "")
+ |name|
+ |full-name|
+ (|sex| "")
+ |birthday-year|
+ |birthday-month|
+ |birthday-day|
+ |email|
+ (|administrator| ""))
+ (if (not (string= |authenticity-token| (token)))
+ '(403 () ("Denied"))
+ (with-connection (db)
+ (let ((id (ignore-errors (parse-integer id)))
+ (user (and id
+ (mito:find-dao
+ 'rails-to-caveman.model::user :id id))))
+ (if (null user)
+ '(500 () ("Could not edit because user doesn't exist.")))
+ (progn
+
+ #| CHAPTER 6 CODE NOT WORKING HERE.
+ =================================
+
+ I could not get this part of the code (setf) to work when
+ following the tutorial. Because of this, I have decided to
+ leave the code the tutorial (in Chapter 6)
+ provided. Hopefully, there are other code examples which
+ will provide an answer to get this part of the code
+ working.
+
+ For now, this route (I.E. when you try to update a user) the site will
+ throw an error and not update it.
+ |#
+
+ (setf (rails-to-caveman.model::number-of user) (parse-integer |number| :junk-allowed t)
+ (rails-to-caveman.model::name-of user) |name|
+ (rails-to-caveman.model::full-name-of user) |full-name|
+ (rails-to-caveman.model::sex-of user) (parse-integer |sex| :junk-allowed t)
+ (rails-to-caveman.model::birthday-of user) (local-time:parse-timestring
+ (format nil "~A-~A-~A"
+ |birthday-year|
+ |birthday-month|
+ |birthday-day|))
+ (rails-to-caveman.model::email-of user) |email|
+ (rails-to-caveman.model::administrator-of user) (eq rails-to-caveman.model::+true+
+ (zerop (parse-integer
+ |administrator|
+ :junk-allowed t))))
+ (mito:save-dao user)
+ (setf (gethash :notice ningle:*session*) "Updated")
+ `(303 (:location ,(format nil "/users/~D" id))))))))
+
+(defroute ("/user" :method :post)
+ (&key |authenticity-token|
+ (|number| "")
+ |name|
+ |full-name|
+ (|sex| "")
+ |birthday-year|
+ |birthday-month|
+ |birthday-day|
+ |email|
+ (|administrator| ""))
+ (if(not(string= |authenticity-token| (token)))
+ '(403 () ("Denied"))
+ (with-connection (db)
+ (let ((user (mito:create-dao
+ 'rails-to-caveman.model::user
+ :number (parse-integer |number| :junk-allowed t)
+ :name |name|
+ :full-name |full-name|
+ :sex (parse-integer |sex| :junk-allowed t)
+ :birthday (local-time:parse-timestring
+ (format nil "~A-~A-~A"
+ |birthday-year|
+ |birthday-month|
+ |birthday-day|))
+ :email |email|
+ :administrator (eq
+ rails-to-caveman.model::+true+
+ (zerop
+ (parse-integer |administrator|
+ :junk-allowed t))))))
+ (setf(gethash :notice ningle:*session*)"Stored!")
+ `(303 (:location ,(format
+ nil "/users/~D"(mito:object-id user))))))))
+
+(defroute delete-user ("/users/:id" :method :delete)
+ (&key |authenticity-token| id)
+ (if (not (string= |authenticity-token| (token)))
+ `(403 (:content-type "text/plain") ("Denied"))
+ (with-connection (db)
+ (let* ((id (ignore-errors (parse-integer id)))
+ (user (and id
+ (mito:find-dao 'rails-to-caveman.model::user
+ :id id))))
+ (if (null user)
+ `(500 (:content-type "text-plain")
+ (,(format nil
+ "~%Could not delete. User doesn't exist. Id ~S"
+ id)))
+ (progn (mito:delete-dao user)
+ (setf (gethash :notice ningle:*session* "Deleted.")
+ `(303 (:location "/users/index")))))))))
+
+(defroute ("/users/:id" :method :post)
+ (&key |authenticity-token|
+ id
+ (|number| "")
+ |name|
+ |full-name|
+ (|sex| "")
+ |birthday-year|
+ |birthday-month|
+ |birthday-day|
+ |email|
+ (|administrator| "")
+ |_method|)
+ (if (not (string= |authenticity-token| (token)))
+ `(403 () ("Denied"))
+ (cond ((string= |_method| "delete")
+ (delete-user
+ (acons "ID" id (lack.request:request-body-parameters
+ ningle:*request*))))
+ ((find |_method| `("" "post"))
+ (with-connection (db)
+ (let ((id (ignore-errors (parse-integer id)))
+ (user (and id (mito:find-dao
+ 'rails-to-caveman.model::user
+ :id id))))
+ (if (null user)
+ '(500 (:content-type "text/plan"
+ ("Could not find the user."))
+ (progn (setf (rails-to-caveman.model::number-of user)
+ (parse-integer |number| :junk-allowed t)
+ (rails-to-caveman.model::name-of user)
+ |name|
+ (rails-to-caveman.model::full-name-of user)
+ (rails-to-caveman.model::sex-of user)
+ (parse-integer |sex| :junk-allowed t)
+ (rails-to-caveman.model::birthday-of user)
+ (local-time:parse-timestring
+ (format nil "~A-~A-~A"
+ |birthday-year|
+ |birthday-month|
+ |birthday-day|))
+ (rails-to-caveman.model::email-of user)
+ |email|
+ (rails-to-caveman.model::administrator user)
+ (eq rails-to-caveman.model::+true+
+ (zerop (parse-integer
+ |administrator|
+ :junk-allowed t))))
+ (mito:save-dao user)
+ (setf (gethash
+ :notice ningle:*session*)
+ "Updated")
+ `(303 (:location ,(format nil
+ "/users/~D"
+ id)))))))))
+ (t `(400 (:content-type "text/plain")
+ (,(format nil "Unsupported method ~S"
+ |_method|)))))))
+
+
+
(defroute "/about" ()
;; about.html should be in the /templates directory.
@@ -198,9 +397,19 @@
((:pan . 2680)
(:glass . 2550)
(:pepper-mill . 4515)
- (:peeler . 945)))))
- ))
+ (:peeler . 945)))))))
+
+(defun token ()
+ "CSRF token."
+ (cdr (assoc "lack.session"
+ (lack.request:request-cookies ningle:*request*)
+ :test #'string=) ;string equality (always forget this)
+ ))
+(defun flash-gethash (key table)
+ (let ((value (gethash key table)))
+ (remhash key table)
+ value))
;;
;; Error pages
diff --git a/static/css/main.css b/static/css/main.css
index a8c8a65..4b8a472 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -80,3 +80,10 @@ div.toolbar {
font-size: 90%;
text-align: right;
}
+
+/* flash */
+p.notice {
+ border: 1px solid blue;
+ padding: 3px;
+ background-color: #ccf;
+}
diff --git a/templates/layouts/app.html b/templates/layouts/app.html
index 5fc7e2c..08b7f17 100644
--- a/templates/layouts/app.html
+++ b/templates/layouts/app.html
@@ -11,6 +11,9 @@
{% include "shared/header.html" %}
+ {% if notice %}
+ {{notice}}
+ {% endif %}
{% block content %}{% endblock %}