Browse Source

Chapter 6 commit (bug with /user/:id route when posting).

This mostly sets up the basic CRUD operations you normally find on a
website. There is some code for CSRF, also.

There is one major bit of malfunctioning coding though.  It is the
'setf' bit of code. I have tried to rearrange the code to see if I can
get it to work but I simply cannot work it out.

I have decided to leave the code as it is described in the
tutorial (for Chapter 6) and hope there is some incite in future
chapters to help me fix the problem.
master
Craig Oates 2 years ago
parent
commit
3d707c5409
  1. 1
      rails-to-caveman.asd
  2. 8
      src/model.lisp
  3. 227
      src/web.lisp
  4. 7
      static/css/main.css
  5. 3
      templates/layouts/app.html
  6. 25
      templates/users/edit.html
  7. 96
      templates/users/form.html
  8. 15
      templates/users/index.html
  9. 21
      templates/users/new.html
  10. 2
      templates/users/show.html

1
rails-to-caveman.asd

@ -9,6 +9,7 @@
#:envy
#:cl-ppcre
#:uiop
#:local-time ; <-- Added Chapter 6
;; for @route annotation
#:cl-syntax-annot

8
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)

227
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

7
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;
}

3
templates/layouts/app.html

@ -11,6 +11,9 @@
{% include "shared/header.html" %}
</header>
<main>
{% if notice %}
<p class="notice">{{notice}}</p>
{% endif %}
{% block content %}{% endblock %}
</main>
<aside id="sidebar">

25
templates/users/edit.html

@ -0,0 +1,25 @@
{% extends "layouts/app.html" %}
{% block title %}
{% lisp (title! "Edit User") %}
{% endblock %}
{% block content %}
<h1>{% lisp (title!) %}</h1>
<div class="toolbar">
<a href="/users/{{user.id}}">Back to user detail</a>
</div>
<form class="edit-user" id="edit-user"
action="/user/{{user.id}}" method="post">
<input name="_method" type="hidden" value="patch"/>
<input name="authenticity-token" type="hidden" value="{{token}}"/>
{% include "users/form.html" %}
<div>
<input type="submit" name="commit" value="edit user"/>
</div>
</form>
{% endblock %}

96
templates/users/form.html

@ -0,0 +1,96 @@
<table class="attr">
<tr>
<th><label for="user-number">Number</label></th>
<td><input size="8" type="text" name="number"
value="{{user.number}}" id="user-number"/></td>
</tr>
<tr>
<th><label for="user-name">Name</label></th>
<td><input type="text" name="name"
value="{{user.name}}"
id="user-name"/></td>
</tr>
<tr>
<th><label for="user-full-name">Full Name</label></th>
<td><input type="text" value="{{user.full-name}}"
name="full-name" id="user-full-name"/></td>
</tr>
<tr>
<th>Sex</th>
<td>
<input type="radio" value="1"
{% ifequal user.sex 1 %}checked="checked"{% endifequal %}
name="sex" id="member-sex-1"/>
<label for="member-sex-1">Male</label>
<input type="radio" value="2"
{% ifequal user.sex 2 %}checked="checked"{% endifequal %}
name="sex" id="member-sex-2"/>
<label for="member-sex-2">Female</label>
</td>
</tr>
<tr>
<th><label for="user-email">Email</label></th>
<td><input type="text" name="email" id="user-email"/></td>
</tr>
<tr>
<th>Administration</th>
<td>
<input name="administrator" type="hidden" value="0"/>
<input type="checkbox" value="1" name="administrator" id="user-administrator"/>
<label for="user-administrator">Administrator</label>
</td>
</tr>
<tr>
<th><label for="user-birthday">Birthday</label></th>
<td>
<select id="member-birthday-li" name="birthday-year">
{{ user.birthday
| lisp: (lambda(timestamp)
(let((current-year(local-time:timestamp-year(local-time:now))))
(loop :for i :upfrom 1940 :to current-year
:with target = (or (and timestamp
(local-time:timestamp-year timestamp))
(- current-year 20))
:collect (format nil "<option value=\"~D\"~@[ ~A~]>~2:*~D</option>~%"
i (when(= target i)
"selected=\"selected\"")))))
| join:""
| safe
}}
</select>
<select id="member-birthday-2i" name="birthday-month">
{{ user.birthday
| lisp: (lambda(timestamp)
(loop :for i :upfrom 1 to 12
:with target = (or (and timestamp
(local-time:timestamp-month timestamp))
1)
:collect (format nil "<option value=\"~D\"~@[ ~A~]>~A</option>~%"
i (when(= target i)
"selected=\"selected\"")
(aref local-time:+month-names+ i))))
| join:""
| safe
}}
</select>
<select id="birthday-3i" name="birthday-day">
{{ user.birthday
| lisp: (lambda(timestamp)
(loop :for i :upfrom 1 to 31
:with target = (or (and timestamp
(local-time:timestamp-day timestamp))
1)
:collect (format nil "<option value=\"~D\"~@[ ~A~]>~2:*~D</option>~%"
i (when(= target i)
"selected=\"selected\""))))
| join:""
| safe
}}
</select>
</td>
</tr>
</table>

15
templates/users/index.html

@ -2,8 +2,8 @@
{% block title %}{% lisp (title! "list of users") %}{% endblock %}
{% block content %}
<h1>{% lisp (title!) %}</h1>
<form class="search" action="/user/search" accept-charset="UTF=8" method="get">
<input name="utf=8" type="hidden" value="v"/>
<form class="search" action="/users/search" accept-charset="UTF=8" method="get">
<input name="utf=8" type="hidden" value="{{token}}"/>
<input type="text" name="q" id="q"/>
<input type="submit" name="commit" value="search" data-disable-with="search"/>
</form>
@ -25,11 +25,12 @@
<td><a href="/users/{{user.id}}">{{user.name}}</a></td>
<td>{{user.full-name}}</td>
<td>
<a href="/user/{{user.id}}/edit">Edit</a> |
<a data-confirm="Really delete it?"
rel="nofollow" data-method="delete"
href="/user/{{user.id}}">Delete
</a>
<a href="/users/{{user.id}}/edit">Edit</a> |
<form action="/users/{{user.id}}" method="post">
<input type="hidden" name="_method" value="delete">
<input type="hidden" name="authenticity-token" value="{{token}}">
<input type="submit" value="Delete {{user.id}}">
</form>
</tr>
{% endfor %}
</tbody>

21
templates/users/new.html

@ -0,0 +1,21 @@
{% extends "layouts/app.html" %}
{% block title %}
{% lisp (title! "New member") %}
{% endblock %}
{% block content %}
<h1>{% lisp (title!) %}</h1>
<p>{{token}}</p>
<form class="new-user" id="new-user" action="/user" method="post">
<input name="authenticity-token" type="hidden" value="{{token}}" />
{% include "users/form.html" %}
<div>
<input type="submit" name="commit" value="create user" />
</div>
</form>
{% endblock %}
{% extends "layouts/app.html" %}
{% block title %}
{% lisp (title! "New member") %}
{% endblock %}

2
templates/users/show.html

@ -3,7 +3,7 @@
{% block content %}
<h1>{% lisp (title!) %}</h1>
<div class="toolbar"><a href="/users/{{id}}/edit">Edit</a></div>
<div class="toolbar"><a href="/users/{{user.id}}/edit">Edit</a></div>
<table class="attr">
<tr>

Loading…
Cancel
Save