( in-package #:cl-user )
( defpackage rails-to-caveman.web
( :use #:cl
#:caveman2
#:rails-to-caveman.config
#:rails-to-caveman.view
#:rails-to-caveman.db
#:rails-to-caveman.model
#:sxql
#:mito )
( :export #:*web* ) )
( in-package #:rails-to-caveman.web )
;; for @route annotation
( syntax:use-syntax :annot )
;;
;; Application
( defclass <web> ( <app> ) ( ) )
( defvar *web* ( make-instance '<web> ) )
( clear-routing-rules *web* )
;;
;; Routing rules
( defroute "/" ( )
( render #P"index.html"
;; Use to pass message to view -- it is expecting a
;; `MESSAGE' item. You do not need to add it, though. Or,
;; you can pass it an empty value/string if you do not want
;; to leave the code commented out.
;; '(:message "This is not a message") ; Added Chapter 3.
' ( :numbers ( 1 2 3 4 5 ) )
) )
( defroute "/users/index" ( )
( render "users/index.html"
` ( :users , ( with-connection ( db )
( mito:select-dao 'rails-to-caveman.model::user
( sxql:order-by :number ) ) ) ) ) )
( defroute "/users/search" ( &key |q| )
( render "users/index.html"
` ( :users , ( with-connection ( db )
( mito:select-dao 'rails-to-caveman.model::user
( sxql:where
` ( :or ( :like :name , |q| )
( :like :full-name , |q| ) ) )
( 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 ) ) ) ) )
( defroute "/about" ( )
;; about.html should be in the /templates directory.
( render #P"about.html" ' ( :page-title "About" ) ) )
( defroute "/hello/:name" ( &key name )
;; Substitutes ':name' with `NAME'.
( format nil "Hello, ~A" name ) )
( defroute "/say/*/to/*" ( &key splat )
;; If route is /say/hello/to/world (in the browser). It will match
;; to /say/hello/to/world. 'hello' and 'world' are the wildcard
;; values in this example.
( format nil "~A" splat ) )
( defroute ( "/hello/([\\w]+)" :regexp t ) ( &key captures )
;; Parse the second part of the URL via a regular expression
;; (regexp). The result of the parsed regexp text is stored in
;; `CAPTURES', hence the use of 'first' in the format string.
( format nil "Hello, ~A!" ( first captures ) ) )
( defroute "/hello/?:name?" ( &key name )
;; Generates two types of routes:
;; 1. /hello/earth
;; 2. /hello?NAME=earth
;; The query string must be in UPPERCASE. Otherwise, the `NAME' will
;; be bound to `nil'.
( format nil "Hello, ~A" name ) )
( defroute "/hello/?:name?" ( &key |name| )
;; If you want the query string in lowercase, enclose `name' (in
;; this case) with vertical bars '|'. This will force you to have
;; only one route (unlike the route above). `NAME' will now bind to
;; `NIL'.
;; 1. /hello?name=earth
;; 2. /hello?NAME=earth <--- no longer works ('earth' binds to nil).
;; 3. /hello/earth <--- no longer works
( format nil "Hello, ~A" |name| ) )
( defroute "/lesson/step*/:name" ( &key splat name )
;; Directory style: Working with Parameters.
;; Example URL: /lesson/step1/Sato
( case ( parse-integer ( car splat ) :junk-allowed t )
( 1 ( format nil "Hello, ~A" name ) ) ) )
( defroute "/lesson/step*" ( &key splat ( |name| "Anonymous" ) )
;; 'Anonymous' is the default value for `|name|'.
;; Query style: Working with Parameters.
;; Example URL: /lesson/step1?name=Sato
( case ( parse-integer ( car splat ) :junk-allowed t )
( 1 ( format nil "Hello, ~A" |name| ) ) ) )
( defroute "/lesson/step*" ( &key splat name )
;; If /lesson/step1 is used, it will be redirected to /lesson/step2,
;; then /lesson/step3 and finally /lesson/step4. No matter where you
;; start (along as it is below 4), the redirects will always take
;; you to /lesson/step4.
;; The example includes `NAME' BUT it is never used. I am keeping it
;; here for consistency between the reference material and this code
;; base.
( case ( parse-integer ( car splat ) :junk-allowed t )
( 1 ' ( 302 ( :location "/lesson/step2" ) ) )
( 2 ' ( 302 ( :location "/lesson/step3" ) ) )
( 3 ' ( 302 ( :location "/lesson/step4" ) ) )
( 4 "Moved to step4" )
;; To be honest, I do not know what this is actually doing. It is
;; mentioned in 'STEP5 Flash' in Chapter 3 (I.E. Tutorial 3:
;; Routing). This applies to case's 5 and 6.
( 5 ( setf ( gethash :notice *session* ) "Move to step6" )
` ( 302 ( :location "/lesson/step6" ) ) )
( 6 ( let ( ( notice ( gethash :notice *session* ) ) )
( remhash :notice *session* ) notice ) )
;; This is part of an example about using djula (which is a
;; template engine used by Caveman2. djula is a port of Python's
;; Django template engine.
;; step7.html should be in the /templates directory.
( 7 ( render "step7.html"
` ( :price , ( floor ( * 2000 1.08 ) ) ) ) )
( 8 ( render "step7.html" ' ( :price 1000 ) ) )
( 9 ( render "step9.html"
' ( :comment "<script>alert('danger')</script>Hello" ) ) )
;; If you dare to embed HTML, add safe after the variable
;; reference on the View side (step10.html).
( 10 ( render "step10.html"
' ( :comment "<strong>safe html</strong>" ) ) )
( 11 ` ( 200 ( :content-type "text/html; charset=utf-8" )
( , ( let ( ( population 704414 ) ( surface 141.31 ) )
( render "step11.html"
` ( :contents
, ( format nil
;; ~D = Decimal
;; ~,,2F = Fixed-Format
;; Floating-Point
;; ~,,2F = Print exactly two
;; digits after the decimal
;; point and many as necessary
;; before the decimal point.
"Population: ~D Floor Surface: ~D Population/Surface: ~,,2F"
population
( floor surface )
( / population surface ) ) ) ) ) ) ) )
( 12 ( render "step11.html"
` ( :contents , ( local-time:format-timestring
nil
( local-time:now )
:format ' ( ( :year 4 ) "/" ( :month 2 ) "/" ( :day 2 ) "(" :short-weekday ") "
( :hour 2 ) ":" ( :min 2 ) ":" ( :sec 2 ) ) ) ) ) )
( 13 ` ( 200 ( :content-type "text/html; charset=utf-8" )
;; This view uses 'format' in step13.html (view in
;; /templates directory).
( , ( render "step13.html" ' ( :population 127767944 ) ) ) ) )
( 14 ( render "step14.html"
;; The view uses a custom filter which was added to
;; view.lisp.
` ( :contents , ( format nil "foo~%bar~%bazz" ) ) ) )
;; The view demonstrates how to form links.
( 15 ( render "step15.html" ) )
;; The view demonstrates how to display images.
;; Images are stored in the /static/images directory.
( 16 ( render "step16.html" ) )
;; Provides control branches which you can navigate via djula in
;; step17.html (/templates directory).
( 17 ` ( 200 ( :content-type "text/html; charset=utf-8" )
;; Adjust `STOCK' to adjust what is viewed in step17.html.
( , ( let ( ( stock 10 ) )
( render "step17.html"
` ( :stock-zerop , ( < 0 stock ) :stock , stock ) ) ) ) ) )
;; This creates a list (cons list) which is then cycled through in
;; /templates/step18.html using the djula template engine..
( 18 ( render "step18.html"
' ( :items
( ( :pan . 2680 )
( :glass . 2550 )
( :pepper-mill . 4515 )
( :peeler . 945 ) ) ) ) )
) )
;;
;; Error pages
( defmethod on-exception ( ( app <web> ) ( code ( eql 404 ) ) )
( declare ( ignore app ) )
( merge-pathnames #P"_errors/404.html"
*template-directory* ) )