Browse Source

Chapter 9 commit.

master
Craig Oates 2 years ago
parent
commit
c267f3d0f5
  1. 2
      app.lisp
  2. 2
      rails-to-caveman.asd
  3. 8
      src/config.lisp
  4. 2
      src/db.lisp
  5. 2
      src/locale.lisp
  6. 8
      src/main.lisp
  7. 63
      src/model.lisp
  8. 40
      src/view.lisp
  9. 185
      src/web.lisp
  10. 4
      templates/articles/form.html
  11. 7
      templates/articles/index.html
  12. 12
      templates/index.html
  13. 4
      templates/shared/sidebar.html

2
app.lisp

@ -1,6 +1,6 @@
(ql:quickload :rails-to-caveman) (ql:quickload :rails-to-caveman)
(defpackage rails-to-caveman.app (defpackage #:rails-to-caveman.app
(:use #:cl) (:use #:cl)
(:import-from #:lack.builder (:import-from #:lack.builder
#:builder) #:builder)

2
rails-to-caveman.asd

@ -32,7 +32,7 @@
((:module "src" ((:module "src"
:components ((:file "main" :depends-on ("config" "view" "db")) :components ((:file "main" :depends-on ("config" "view" "db"))
(:file "web" :depends-on ("view" "model")) (:file "web" :depends-on ("view" "model"))
(:file "view" :depends-on ("config")) (:file "view" :depends-on ("locale" "config"))
(:file "db" :depends-on ("config")) (:file "db" :depends-on ("config"))
(:file "model" :depends-on ("db")) (:file "model" :depends-on ("db"))
(:file "locale" :depends-on ("config")) ; <--- This! (:file "locale" :depends-on ("config")) ; <--- This!

8
src/config.lisp

@ -1,11 +1,11 @@
(in-package #:cl-user) ;; (in-package #:cl-user)
(defpackage rails-to-caveman.config (defpackage #:rails-to-caveman.config
(:use #:cl) (:use #:cl)
(:import-from #:envy (:import-from #:envy
#:config-env-var #:config-env-var
#:defconfig) #:defconfig)
(:import-from #:rails-to-caveman.locale (:import-from #:uiop
#:define-dictionary) #:getenv)
(:export #:config (:export #:config
#:*application-root* #:*application-root*
#:*static-directory* #:*static-directory*

2
src/db.lisp

@ -1,4 +1,4 @@
(in-package #:cl-user) ;; (in-package #:cl-user)
(defpackage rails-to-caveman.db (defpackage rails-to-caveman.db
(:use :cl) (:use :cl)
(:import-from #:rails-to-caveman.config (:import-from #:rails-to-caveman.config

2
src/locale.lisp

@ -1,4 +1,4 @@
(in-package #:cl-user) ;; (in-package #:cl-user)
(defpackage #:rails-to-caveman.locale (defpackage #:rails-to-caveman.locale
(:use #:cl) (:use #:cl)
(:export #:define-dictionary (:export #:define-dictionary

8
src/main.lisp

@ -1,8 +1,8 @@
(in-package #:cl-user) ;; (in-package #:cl-user)
(defpackage rails-to-caveman (defpackage #:rails-to-caveman
(:use #:cl) (:use #:cl)
(:import-from #:rails-to-caveman.config ;; (:import-from #:rails-to-caveman.config
#:config) ;; #:config)
(:import-from #:clack (:import-from #:clack
#:clackup) #:clackup)
(:export #:start (:export #:start

63
src/model.lisp

@ -1,15 +1,16 @@
(in-package #:cl-user) ;; (in-package #:cl-user)
(defpackage rails-to-caveman.model (defpackage #:rails-to-caveman.model
(:use #:cl (:use #:cl
#:rails-to-caveman.db #:rails-to-caveman.db
#:mito #:mito
#:local-time #:local-time)
#:ratify ;; #:ratify
#:ratify-types ;; #:ratify-types)
#:cl-ppcre) ;; #:cl-ppcre)
(:import-from #:ratify-types (:import-from #:ratify
#:parse-string #:test-email)
) ;; (:import-from #:cl-ppcre
;; #:parse-string)
(:export #:user (:export #:user
#:validate-user #:validate-user
#:seeds #:seeds
@ -391,6 +392,48 @@ reference for future projects. This is a learning project after all.
(:assert (<= (length body) 2000))) (:assert (<= (length body) 2000)))
(date-released (:require t) (date-released (:require t)
(:key #'local-time:parse-timestring)) (:key #'local-time:parse-timestring))
(date-expired (:key #'local-time:parse-timestring)) (date-expired (:key #'local-time:parse-timestring)
(:assert (local-time:timestamp<
(date-released-of article)
date-expired)
"Date expired too old"))
(member-only (:require t) (member-only (:require t)
(:key (lambda(x)(zerop(parse-integer x)))))))) (:key (lambda(x)(zerop(parse-integer x))))))))
(defmethod initialize-instance
:around ((o article)
&rest args
&key date-released
released-year
released-month
released-day
released-hour
released-min
date-expiration
expired-year
expired-month
expired-day
expired-hour
expired-min
no-expiration-p
&allow-other-keys)
(apply #'call-next-method o
`(,@ (when (and (null date-released) released-year)
`(:date-released
,(format nil "~A-~A-~AT~A:~A:00"
released-year
released-month
released-day
released-hour
released-min)))
,@(when (and (not no-expiration-p)
(null date-expiration)
expired-year)
`(:date-expired
,(format nil "~A-~A-~AT~A:~A:00"
expired-year
expired-month
expired-day
expired-hour
expired-min)))
,@args)))

40
src/view.lisp

@ -1,7 +1,7 @@
(in-package #:cl-user) ;; (in-package #:cl-user)
(defpackage #:rails-to-caveman.view (defpackage #:rails-to-caveman.view
(:use #:cl (:use #:cl)
#:rails-to-caveman.locale) ;; #:rails-to-caveman.locale)
(:import-from #:rails-to-caveman.config (:import-from #:rails-to-caveman.config
#:*template-directory*) #:*template-directory*)
(:import-from #:caveman2 (:import-from #:caveman2
@ -41,20 +41,16 @@
(defpackage rails-to-caveman.djula (defpackage rails-to-caveman.djula
(:use #:cl (:use #:cl
#:3bmd) #:3bmd
(:import-from #:rails-to-caveman.config #:rails-to-caveman.locale
#:config #:cl-who)
#:appenv
#:developmentp
#:productionp
#:rails-to-caveman.locale)
(:import-from #:caveman2 (:import-from #:caveman2
#:url-for) #:url-for)
(:import-from #:cl-ppcre (:import-from #:cl-ppcre
#:regexp-replace-all) #:regexp-replace-all)
(:export #:title! ; Added in Chapter 3 (Mockup section). (:export #:title! ; Added in Chapter 3 (Mockup section).
) #:date-options
) ))
;; in-package and let code added in Chapter 3, also. This is ;; in-package and let code added in Chapter 3, also. This is
;; preperation for Switching Layout Templates section. ;; preperation for Switching Layout Templates section.
(in-package #:rails-to-caveman.djula) ; Make sure this states your app name. (in-package #:rails-to-caveman.djula) ; Make sure this states your app name.
@ -87,3 +83,23 @@
(with-output-to-string (s) (with-output-to-string (s)
(3bmd:parse-string-and-print-to-stream (3bmd:parse-string-and-print-to-stream
(ppcre:regex-replace-all #\newline it "<br>") s))) (ppcre:regex-replace-all #\newline it "<br>") s)))
#| LACK OF 'DJULA' DOES NOT MEAN IT IS NOT PART OF IT (date-options)
====================================================================
Even though it does not have 'djula' attached to it. This is a
filter which is applied in '/templates/articles/forms.html'
primarily. The guide (Chapter 9) does not state where to put this
function but you get errors when you try and render the form in the
browser and that is where it specifies there is no 'djula filter'
function going by that name.
|#
(defun date-options (&key (start 0) (end 0) (target start) labels)
(loop :for i
:upfrom start
:to end :collect
(cl-who:with-html-output-to-string(*standard-output*)
((:option :value i :selected (when(eql i target) "selected"))
(princ
(if labels (aref labels i) i))))
:into options
:finally (return (format nil "~{~A~%~}" options))))

185
src/web.lisp

@ -1,14 +1,17 @@
(in-package #:cl-user) ;; (in-package #:cl-user)
(defpackage rails-to-caveman.web (defpackage #:rails-to-caveman.web
(:use #:cl (:use #:cl
#:caveman2 #:caveman2
#:rails-to-caveman.config #:rails-to-caveman.config
#:rails-to-caveman.view #:rails-to-caveman.view
#:rails-to-caveman.db
#:rails-to-caveman.model #:rails-to-caveman.model
#:sxql #:sxql
#:mito #:mito
#:cl-who) #:cl-who)
(:import-from #:rails-to-caveman.db
#:connection-settings
#:db
#:with-connection)
(:export #:*web*)) (:export #:*web*))
(in-package #:rails-to-caveman.web) (in-package #:rails-to-caveman.web)
@ -26,16 +29,18 @@
;; Routing rules ;; Routing rules
(defroute "/" () (defroute "/" ()
(render #P"index.html" "Top page"
;; Use to pass message to view -- it is expecting a (let ((articless (articles-make 5)))
;; `MESSAGE' item. You do not need to add it, though. Or, (format t "[INFO] ~a" articless)
;; you can pass it an empty value/string if you do not want (render #P"index.html" `(:notice ,(flash-gethash :notice ningle:*session*)
;; to leave the code commented out. :user,(when (hermetic:logged-in-p)
(current-user))
;; '(:message "This is not a message") ; Added Chapter 3. ,@(roles)
`(:numbers (1 2 3 4 5) :token ,(token)
:token ,(token) :alert ,(flash-gethash :alert ningle:*session*)
:alert "Bitchin"))) ;; :news ,articles
:blogs (1 2 3 4 5)
:articles ,articless))))
(defroute "/users/index"() (defroute "/users/index"()
(render "users/index.html" (render "users/index.html"
@ -477,21 +482,17 @@ nil "/users/~D"(mito:object-id user))))))))
'(303 (:location "/"))))) '(303 (:location "/")))))
(defroute "/articles" () (defroute "/articles" ()
;; (format t "[INFO] ~A and ~%" articles) (render "articles/index.html"
(if (not (hermetic:logged-in-p)) `(:user ,(current-user)
(render "/index.html" :news ,(articles-make 5)
`(:news (1 2 3 4 5) :blogs (1 2 3 4 5)
:blogs (1 2 3 4 5) :token ,(token)
:token ,(token) :articles
:alert "Not logged in you muppet.")) ,(hermetic:auth (:adminstrator)
(render "articles/index.html" (with-connection (db)
`(:user ,(current-user) (mito:retrieve-dao 'rails-to-caveman.model::article))
:news (1 2 3 4 5) (articles-make 5))
:blogs (1 2 3 4 5) ,@(roles))))
:token ,(token)
:articles ,(with-connection (db)
(mito:retrieve-dao 'rails-to-caveman.model::article))
,@(roles)))))
(defroute ("/articles" :method :post) (&key method) (defroute ("/articles" :method :post) (&key method)
(cond ((string= "put" method) (new-article (cond ((string= "put" method) (new-article
@ -499,15 +500,30 @@ nil "/users/~D"(mito:object-id user))))))))
(t `(401 () (,(format nil "Unknown method ~S" method)))))) (t `(401 () (,(format nil "Unknown method ~S" method))))))
(defroute "/articles/:id" (&key id) (defroute "/articles/:id" (&key id)
(if (null (ignore-errors (parse-integer id))) ;; (if (null (ignore-errors (parse-integer id)))
(myway.mapper:next-route) ;; myway.mapper:next-route)
;; (let ((article (with-connection (db)
;; (mito:find-dao 'rails-to-caveman.model::article
;; :id id)))
;; (if (find :administrator (hermetic:roles))
;; #0=(render "articles/show.html"
;; `(:articles (1 2 3 4 5) ;,article
;; ,@(roles)
;; :token ,(token)
;; :user ,(current-user)
;; ;; :news ,(articles-make 5)
;; :news (1 2 3 4 5)
;; :blogs (1 2 3 4 5))
;; (if (publicp article)
;; #0# '(404 ()
;; ("No articles"))))))))
(render "articles/show.html" (render "articles/show.html"
`(:article ,(with-connection (db) `(:article ,(with-connection (db)
(mito:find-dao 'rails-to-caveman.model::article :id id)) (mito:find-dao 'rails-to-caveman.model::article :id id))
,@(roles) ,@(roles)
:user ,(current-user) :user ,(current-user)
:news (1 2 3 4 5) :news (1 2 3 4 5)
:blogs (1 2 3 4 5))))) :blogs (1 2 3 4 5))))
(defroute ("/articles/:id" :method :post)(&key method id) (defroute ("/articles/:id" :method :post)(&key method id)
(cond ((and (string= "post" method) (cond ((and (string= "post" method)
@ -522,6 +538,7 @@ nil "/users/~D"(mito:object-id user))))))))
(defroute destroy-article ("/article/:id" :method :delete) (defroute destroy-article ("/article/:id" :method :delete)
(&key authenticity-token id) (&key authenticity-token id)
(step
(if (not (string= authenticity-token(token))) (if (not (string= authenticity-token(token)))
'(401()("Denied")) '(401()("Denied"))
(if (not (hermetic:logged-in-p)) (if (not (hermetic:logged-in-p))
@ -530,7 +547,7 @@ nil "/users/~D"(mito:object-id user))))))))
(myway.mapper:next-route) (myway.mapper:next-route)
(progn (with-connection (db) (progn (with-connection (db)
(mito:delete-by-values 'rails-to-caveman.model::article :id id)) (mito:delete-by-values 'rails-to-caveman.model::article :id id))
`(303 (:location "/articles"))))))) `(303 (:location "/articles"))))))))
(defroute "/articles/new" () (defroute "/articles/new" ()
(render "articles/new.html" (render "articles/new.html"
@ -541,6 +558,34 @@ nil "/users/~D"(mito:object-id user))))))))
,@(roles) ,@(roles)
:token ,(token)))) :token ,(token))))
(defroute new-article ("/articles" :method :put)
(&key authenticity-token no-expiration-p)
(if (not (string= authenticity-token (token)))
'(401 () ("Denied"))
(let ((render-args `(:user
,(current-user)
,@(roles)
:token ,(token)
:news ,(articles 5)
:blogs (1 2 3 4 5))))
(multiple-value-bind (article errors)
(rails-to-caveman.model::validate-article
(apply #'make-instance 'rails-to-caveman.model::article
(request-params (lack.request:request-body-parameters
ningle:*request*))))
(if errors
(render "articles/new.html"
(list* :article article
:errors errors
:no-expiration-p no-expiration-p
render-args))
(progn (with-connection (db)
(mito:insert-dao article)
(setf (gethash :notice ningle:*session*)"Stored")
`(303 (:location
,(format nil "/articles/~D"
(mito:object-id article)))))))))))
(defroute new-article ("/articles" :method :put) (defroute new-article ("/articles" :method :put)
(&key authenticity-token (&key authenticity-token
released-year released-year
@ -600,7 +645,10 @@ nil "/users/~D"(mito:object-id user))))))))
(mito:object-id article))))))))))) (mito:object-id article)))))))))))
(defroute "/articles/:id/edit" (&key id) (defroute "/articles/:id/edit" (&key id)
(step
(render "articles/edit.html" (render "articles/edit.html"
`(:article ,(with-connection (db) `(:article ,(with-connection (db)
(mito:find-dao (mito:find-dao
@ -609,7 +657,7 @@ nil "/users/~D"(mito:object-id user))))))))
,@(roles) ,@(roles)
:token ,(token) :token ,(token)
:news (1 2 3 4 5) :news (1 2 3 4 5)
:blogs (1 2 3 4 5)))) :blogs (1 2 3 4 5)))))
(defun edit-article (request) (defun edit-article (request)
(step (step
@ -638,6 +686,7 @@ nil "/users/~D"(mito:object-id user))))))))
(let ((article (with-connection (db) (let ((article (with-connection (db)
(mito:find-dao (mito:find-dao
'rails-to-caveman.model::article :id id)))) 'rails-to-caveman.model::article :id id))))
;; (format t "[INFO] ~A" (getf article 'title))
(setf (rails-to-caveman.model::title-of article) title (setf (rails-to-caveman.model::title-of article) title
(rails-to-caveman.model::body-of article) body (rails-to-caveman.model::body-of article) body
(rails-to-caveman.model::date-released-of article) (rails-to-caveman.model::date-released-of article)
@ -656,7 +705,7 @@ nil "/users/~D"(mito:object-id user))))))))
expired-min) expired-min)
(rails-to-caveman.model::member-only article) member-only) (rails-to-caveman.model::member-only article) member-only)
(multiple-value-bind (article errors) (multiple-value-bind (article errors)
(rails-to-caveman.model::validate-article (rails-to-caveman.model::validate-article article)
(if errors (render "articles/edit.html" (if errors (render "articles/edit.html"
`(:article ,article `(:article ,article
:errors ,errors :errors ,errors
@ -669,7 +718,7 @@ nil "/users/~D"(mito:object-id user))))))))
(mito:save-dao article)) (mito:save-dao article))
(setf (gethash :notice ningle:*session*) "Updated") (setf (gethash :notice ningle:*session*) "Updated")
`(303 (:location ,(format nil "/articles/~D" `(303 (:location ,(format nil "/articles/~D"
(mito:object-id article)))))))))))))) (mito:object-id article)))))))))))))
(defroute "/about" () (defroute "/about" ()
@ -869,13 +918,59 @@ nil "/users/~D"(mito:object-id user))))))))
'rails-to-caveman.model::user 'rails-to-caveman.model::user
:id (gethash :id ningle:*session*)))) :id (gethash :id ningle:*session*))))
(defun date-options (&key (start 0) (end 0) (target start) labels) (defun publicp (article)
(loop :for i (local-time:timestamp< (date-released-of article)
:upfrom start :to end (local-time:now)
:collect (cl-who:with-html-output-to-string (*standard-output*) (date-expired-of article)))
((:option
:value i (defun format-to-date (&optional(time (local-time:now)))
:selected (when (eql i target) "selected")) (local-time:format-timestring
(princ (if labels (aref labels i) i)))) nil
:into options time
:finally (return (format nil "~{~A~%~}" options)))) :format '((:year 4)"-"(:month 2)"-"(:day 2))))
(defun articles (n &key (logged-in-p (hermetic:logged-in-p))
(user(and logged-in-p (current-user))))
(if logged-in-p
(if (rails-to-caveman.model::administrator-of user)
(with-connection (db)
(mito:select-dao 'rails-to-caveman.model::article
(sxql:order-by (:desc :date-released))
(sxql:limit n))
(let ((now (rails-to-caveman.web::format-to-date)))
(mito:select-dao 'rails-to-caveman.model::article
(sxql:where (:and (:< :date-released now)))
;; (:< now :date-expired)))
(sxql:order-by (:desc :date-released))
(sxql:limit n)))))
(let ((now (rails-to-caveman.web::format-to-date)))
(with-connection (db)
(mito:select-dao 'rails-to-caveman.model::article
(sxql:where (:and (:< :date-released now)
(:< now :date-expired)
(:= :only-member rails-to-caveman.model::+false+)))
(sxql:order-by (:desc :date-released))
(sxql:limit n))))))
;; (defun articles (n &key (logged-in-p(hermetic:logged-in-p))
;; (user (and logged-in-p (current-user))))
;; (if logged-in-p
;; (if (your-app.model::administrator-of user)
;; (mito:select-dao 'your-app.model::article
;; (sxql:order-by (:desc :date-released))
;; (sxql:limit n))
;; (let ((now(your-app.web::format-to-date)))
;; (mito:select-dao 'your-app.model::article
;; (sxql:where
;; (:and (:< :date-released now)
;; (:< now :date-expired)))
;; (sxql:order-by (:desc :date-released))
;; (sxql:limit n))))
;; (let ((now(your-app.web::format-to-date)))
;; (mito:select-dao 'your-app.model::article
;; (sxql:where (:and (:< :date-released now)
;; (:< now :date-expired)
;; (:= :only-member your-app.model::+false+)))
;; (sxql:order-by(:desc :date-released))
;; (sxql:limit n)))))

4
templates/articles/form.html

@ -62,6 +62,10 @@
<tr> <tr>
<th><label for="expired-year">Date expired</label></th> <th><label for="expired-year">Date expired</label></th>
<td> <td>
<div>
<label for="no-expiration">No expiration</label>
<input type="checkbox" {% if no-expiration-p %}checked{% endif %}
name="NO-EXPIRATION-P" id="no-expiration"/>
<select id="expired-year" name="EXPIRED-YEAR"> <select id="expired-year" name="EXPIRED-YEAR">
{{ article.date-expired {{ article.date-expired
| lisp: (lambda(arg) | lisp: (lambda(arg)

7
templates/articles/index.html

@ -19,12 +19,13 @@
{% for article in articles %} {% for article in articles %}
<tr> <tr>
<td><a href="/articles/{{article.id}}">{{article.title}}</a></td> <td><a href="/articles/{{article.id}}">{{article.title}}</a></td>
<td>{{ article.date-released <td>{{article.date-released
| date: ((:year 4)"/"(:month 2)"/"(:day 2)" "(:hour 2)":"(:min 2)) | date: ((:year 4)"/"(:month 2)"/"(:day 2)" "(:hour 2)":"(:min 2))
}}</td> }}
</td>
<td> <td>
<a href="/articles/{{article.id}}/edit">Edit</a>| <a href="/articles/{{article.id}}/edit">Edit</a>|
<form action="/articles/{{article.id}}/delete" method="post"> <form action="/articles/{{article.id}}" method="post">
<input type="hidden" name="AUTHENTICITY-TOKEN" value="{{token}}"> <input type="hidden" name="AUTHENTICITY-TOKEN" value="{{token}}">
<input type="hidden" name="METHOD" value="delete"> <input type="hidden" name="METHOD" value="delete">
<input type="submit" value="Delete"> <input type="submit" value="Delete">

12
templates/index.html

@ -23,9 +23,15 @@
This message is passed from the router in /src/web.lisp. There is This message is passed from the router in /src/web.lisp. There is
no guarantee it will contain anything n it, though. no guarantee it will contain anything n it, though.
--> -->
<h2>{{ message }}</h2>
{% for b in numbers %} {% for article in articles %}
<li><a href="#">Blog Header</a></li> <h2>{{article.title}}</h2>
<p>{{article.body | truncatechars:80}}
<a href="/article/{{article.id}}">More</a>
</p>
{% endfor %}
{% for blog in blogs %}
{% endfor %} {% endfor %}
{% if alert %} {% if alert %}

4
templates/shared/sidebar.html

@ -3,8 +3,8 @@
{% endif %} {% endif %}
<h2>Latest News</h2> <h2>Latest News</h2>
<ul> <ul>
{% for n in news %} {% for n in articles %}
<li><a href="#">News Header</a></li> <li><a href="/articles/{{n.id}}">{{n.title}}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>

Loading…
Cancel
Save