44
Not found
Whoops. Looks like this page doesn't exist ¯\_(ツ)_/¯.
diff --git a/Makefile b/Makefile index 2b107ce..50a12fe 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ run: ./hugo serve build: - ./hugo build --gc --minify && cp -r public/* docs && echo "copied to docs/. Done." + ./hugo build --gc --minify && cp -r public/* docs && git add docs/* && echo "copied to docs/. Done." publish: git commit -m "publish" && git push diff --git a/content/_index.md b/content/_index.md index d8b5163..c22fd65 100644 --- a/content/_index.md +++ b/content/_index.md @@ -55,7 +55,7 @@ Cookbook](https://lispcookbook.github.io/cl-cookbook/). ## Contact -We are @vindarel on [Lisp's Discord server](https://discord.gg/hhk46CE) and Mastodon. +We are @vindarel on [Lisp's Discord server](https://discord.gg/hhk46CE) and [Mastodon](https://framapiaf.org/@vindarel). diff --git a/content/building-blocks/PUT.md b/content/building-blocks/PUT.md new file mode 100644 index 0000000..36b0b01 --- /dev/null +++ b/content/building-blocks/PUT.md @@ -0,0 +1,18 @@ ++++ +title = "PUT and request parameters" +weight = 145 ++++ + +To access the body parameters of a PUT request, one must add `:PUT` to +`hunchentoot:*methods-for-post-parameters*`, which defaults to only +`(:POST)`: + +```lisp +(push :put hunchentoot:*methods-for-post-parameters*) +``` + +This parameter: + +> is a list of the request method types (as keywords) for which Hunchentoot will try to compute POST-PARAMETERS. + +No such setting is required with Lack and Ningle. diff --git a/content/building-blocks/database.md b/content/building-blocks/database.md index 8cd4376..ce7b80d 100644 --- a/content/building-blocks/database.md +++ b/content/building-blocks/database.md @@ -232,7 +232,7 @@ Once we edit the table definition (aka the class definition), Mito will (by default) automatically migrate it. There is much more to say, but we refer you to Mito's good -documentation and to the Cookbook. +documentation and to the Cookbook (links below). ## How to integrate the databases into the web frameworks diff --git a/content/building-blocks/flash-messages.lisp b/content/building-blocks/flash-messages.lisp new file mode 100644 index 0000000..1945bc2 --- /dev/null +++ b/content/building-blocks/flash-messages.lisp @@ -0,0 +1,68 @@ + +;;; +;;; Demo of flash messages: +;;; anywhere in a route, easily add flash messages to the session. +;;; They are rendered at the next rendering of a template. +;;; They are removed from the session once rendered. +;;; + +;; inspired by https://github.com/rudolfochrist/booker/blob/main/app/controllers.lisp + +(uiop:add-package-local-nickname :ht :hunchentoot) + +(djula:add-template-directory ".") + +(defparameter *flash-template* (djula:compile-template* "flash-template.html")) + +(defparameter *port* 9876) + +(defvar *server* nil "Our Hunchentoot acceptor") + +(defun flash (type message) + "Add a flash message in the session. + + TYPE: can be anything as you do what you want with it in the template. + Here, it is a string that represents the Bulma CSS class for notifications: is-primary, is-warning etc. + MESSAGE: string" + (let* ((session (ht:start-session)) + (flash (ht:session-value :flash session))) + (setf (ht:session-value :flash session) + ;; With a cons, REST returns 1 element + ;; (when with a list, REST returns a list) + (cons (cons type message) flash)))) + +;;; delete flash after it is used. +(defmethod ht:handle-request :after (acceptor request) + (ht:delete-session-value :flash)) + +#+another-solution +(defun render (template &rest args) + (apply + #'djula:render-template* template nil + (list* + :flashes (or (ht:session-value :flash) + (list (cons "is-primary" "No more flash messages were found in the session. This is a default notification."))) + args))) + + +(easy-routes:defroute flash-route ("/flash/" :method :get) () + #-another-solution + (djula:render-template* *flash-template* nil + :flashes (or (ht:session-value :flash) + (list (cons "is-primary" "No more flash messages were found in the session. This is a default notification.")))) + #+another-solution + (render *flash-template*) + ) + +(easy-routes:defroute flash-redirect-route ("/tryflash/") () + (flash "is-warning" "This is a warning message held in the session. It should appear only once: reload this page and you won't see the flash message again.") + (ht:redirect "/flash/")) + +(defun start (&key (port *port*)) + (format t "~&Starting the web server on port ~a~&" port) + (force-output) + (setf *server* (make-instance 'easy-routes:easy-routes-acceptor :port port)) + (ht:start *server*)) + +(defun stop () + (ht:stop *server*)) diff --git a/content/building-blocks/flash-messages.md b/content/building-blocks/flash-messages.md new file mode 100644 index 0000000..824ba85 --- /dev/null +++ b/content/building-blocks/flash-messages.md @@ -0,0 +1,324 @@ ++++ +title = "Flash messages" +weight = 36 ++++ + +Flash messages are temporary messages you want to show to your +users. They should be displayed once, and only once: on a subsequent +page load, they don't appear anymore. + + + +They should specially work across route redirects. So, they are +typically created in the web session. + +Handling them involves those steps: + +- create a message in the session + - have a quick and easy function to do this +- give them as arguments to the template when rendering it +- have some HTML to display them in the templates +- remove the flash messages from the session. + +## Getting started + +If you didn't follow the tutorial, quickload those libraries: + +```lisp +(ql:quickload '("hunchentoot" "djula" "easy-routes")) +``` + +We also introduce a local nickname, to shorten the use of `hunchentoot` to `ht`: + +```lisp +(uiop:add-package-local-nickname :ht :hunchentoot) +``` + +Add this in your .lisp file if you didn't already, they +are typical for our web demos: + +~~~lisp +(defparameter *port* 9876) +(defvar *server* nil "Our Hunchentoot acceptor") + +(defun start (&key (port *port*)) + (format t "~&Starting the web server on port ~a~&" port) + (force-output) + (setf *server* (make-instance 'easy-routes:easy-routes-acceptor :port port)) + (ht:start *server*)) + +(defun stop () + (ht:stop *server*)) +~~~ + + +## Create flash messages in the session + +This is our core function to quickly pile up a flash message to the web session. + +The important bits are: + +- we ensure to create a web session with `ht:start-session`. +- the `:flash` session object stores *a list* of flash messages. +- we decided that a flash messages holds those properties: + - its type (string) + - its message (string) + + +~~~lisp +(defun flash (type message) + "Add a flash message in the session. + + TYPE: can be anything as you do what you want with it in the template. + Here, it is a string that represents the Bulma CSS class for notifications: is-primary, is-warning etc. + MESSAGE: string" + (let* ((session (ht:start-session)) ;; <---- ensure we started a web session + (flash (ht:session-value :flash session))) + (setf (ht:session-value :flash session) + ;; With a cons, REST returns 1 element + ;; (when with a list, REST returns a list) + (cons (cons type message) flash)))) +~~~ + +Now, inside any route, we can call this function to add a flash message to the session: + +```lisp +(flash "warning" "You are liking Lisp") +``` + +It's easy, it's handy, mission solved. Next. + +## Delete flash messages when they are rendered + +For this, we use Hunchentoot's life cycle and CLOS-orientation: + +```lisp +;; delete flash after it is used. +;; thanks to https://github.com/rudolfochrist/booker/blob/main/app/controllers.lisp for the tip. +(defmethod ht:handle-request :after (acceptor request) + (ht:delete-session-value :flash)) +``` + +which means: after we have handled a request, delete the +`:flash` object from the session. + +{{% notice warning %}} + +If your application sends API requests in JavaScript, they can delete flash messages without you noticing. Read more below. + +An external API request (from the command line for example) is not +concerned, as it doesn't carry Hunchentoot session cookies. + +{{% /notice %}} + + +## Render flash messages in templates + +### Set up Djula templates + +Create a new `flash-template.html` file. + +~~~lisp +(djula:add-template-directory "./") +(defparameter *flash-template* (djula:compile-template* "flash-template.html")) +~~~ + +{{% notice info %}} + +You might need to change the current working +directory of your Lisp REPL to the directory of your .lisp file, so +that `djula:compile-template*` can find your template. Use the short +command `,cd` or `(swank:set-default-directory "/home/you/path/to/app/")`. +See also `asdf:system-relative-pathname system directory`. + +{{% /notice %}} + +### HTML template + +This is our template. We use [Bulma +CSS](https://bulma.io/documentation/elements/notification/) to pimp it +up and to use its notification blocks. + +```html + + + +
+ + + +
```
+
+or
+
+```html
+
+```
+
+where the file `src/static/test.js` could be
+
+```js
+console.log("hello");
+```
diff --git a/content/building-blocks/templates.md b/content/building-blocks/templates.md
index 69e8ee8..6abbf6f 100644
--- a/content/building-blocks/templates.md
+++ b/content/building-blocks/templates.md
@@ -29,6 +29,25 @@ and then we can declare and compile the ones we use, for example::
(defparameter +products.html+ (djula:compile-template* "products.html"))
~~~
+{{% notice info %}}
+
+If you get an error message when calling `add-template-directory` like this
+
+```
+The value
+ #P"/home/user/…/project/src/templates/index.html"
+
+is not of type
+ STRING
+from the function type declaration.
+ [Condition of type TYPE-ERROR]
+```
+
+then update your Quicklisp dist or clone Djula in `~/quicklisp/local-projects/`.
+
+{{% /notice %}}
+
+
A Djula template looks like this:
```html
diff --git a/content/building-blocks/user-log-in.md b/content/building-blocks/user-log-in.md
index eed9dc2..b2924bc 100644
--- a/content/building-blocks/user-log-in.md
+++ b/content/building-blocks/user-log-in.md
@@ -326,6 +326,9 @@ Remarks:
## Full code
```lisp
+(defpackage :myproject
+ (:use :cl))
+
(in-package :myproject)
;; User-facing paramaters.
@@ -433,8 +436,7 @@ Remarks:
;; Server.
(defun start-server (&key (port *port*))
(format t "~&Starting the login demo on port ~a~&" port)
- (unless *server*
- (setf *server* (make-instance 'hunchentoot:easy-acceptor :port port)))
+ (setf *server* (make-instance 'easy-routes:easy-routes-acceptor :port port))
(hunchentoot:start *server*))
(defun stop-server ()
diff --git a/content/building-blocks/users-and-passwords.md b/content/building-blocks/users-and-passwords.md
index cf74953..9ab5e75 100644
--- a/content/building-blocks/users-and-passwords.md
+++ b/content/building-blocks/users-and-passwords.md
@@ -4,15 +4,13 @@ weight = 130
+++
We don't know of a Common Lisp framework that will create users and
-roles for you and protect your routes. You'll have to either write
-some Lisp, either use an external tool (such as Keycloak) that will
-provide all the user management.
+roles for you and protect your routes all at the same time. We have
+building blocks but you'll have to write some glue Lisp code.
-{{% notice info %}}
+You can also turn to external tools (such as [Keycloak](https://www.keycloak.org/) or [Tesseral](https://tesseral.com/)) that will
+provide all the industrial-grade user management.
-Stay tuned! We are on to something.
-
-{{% /notice %}}
+If you like the Mito ORM, look at [mito-auth](https://github.com/fukamachi/mito-auth/) and [mito-email-auth](https://github.com/40ants/mito-email-auth).
## Creating users
@@ -55,19 +53,28 @@ Did it work? Run `SELECT * FROM users;`.
## Encrypting passwords
-### With cl-pass
+### With cl-bcrypt
-[cl-pass](https://github.com/eudoxia0/cl-pass) is a password hashing and verification library. It is as simple to use as this:
+[cl-bcrypt](https://github.com/dnaeon/cl-bcrypt) is a password hashing and verification library. It is as simple to use as this:
```lisp
-(cl-pass:hash "test")
-;; "PBKDF2$sha256:20000$5cf6ee792cdf05e1ba2b6325c41a5f10$19c7f2ccb3880716bf7cdf999b3ed99e07c7a8140bab37af2afdc28d8806e854"
-(cl-pass:check-password "test" *)
-;; t
-(cl-pass:check-password "nope" **)
-;; nil
+CL-USER> (defparameter *password*
+ (bcrypt:make-password "my-secret-password"))
+*PASSWORD*
```
+and you can specify another salt, another cost factor and another algorithm identifier.
+
+Then you can use `bcrypt:encode` to get a string reprentation of the password:
+
+~~~lisp
+CL-USER> (bcrypt:encode *password*)
+"$2a$16$ClVzMvzfNyhFA94iLDdToOVeApbDppFru3JXNUyi1y1x6MkO0KzZa"
+~~~
+
+and you decode a password with `decode`.
+
+
### Manually (with Ironclad)
In this recipe we do the encryption and verification ourselves. We use the de-facto standard
@@ -124,3 +131,12 @@ library.
```
*Credit: `/u/arvid` on [/r/learnlisp](https://www.reddit.com/r/learnlisp/comments/begcf9/can_someone_give_me_an_eli5_on_hiw_to_encrypt_and/)*.
+
+## See also
+
+* [cl-authentic](https://github.com/charJe/cl-authentic) - Password management for Common Lisp (web) applications. [LLGPL][8].
+ - safe password storage: cleartext-free, using your choice of hash algorithm through ironclad, storage in an SQL database,
+ - password reset mechanism with one-time tokens (suitable for mailing to users for confirmation),
+ - user creation optionally with confirmation tokens (suitable for mailing to users),
+
+and more on the awesome-cl list.
diff --git a/content/isomorphic web frameworks/weblocks.md b/content/isomorphic web frameworks/weblocks.md
index b856895..bc8259d 100644
--- a/content/isomorphic web frameworks/weblocks.md
+++ b/content/isomorphic web frameworks/weblocks.md
@@ -13,7 +13,6 @@ The Reblocks's demo we will build is a TODO app:

----
{{% notice note %}}
diff --git a/content/see also/_index.md b/content/see also/_index.md
index 51233d2..ff7a817 100644
--- a/content/see also/_index.md
+++ b/content/see also/_index.md
@@ -11,10 +11,6 @@ Other tutorials:
Project skeletons and demos:
- [cl-cookieweb](https://github.com/vindarel/cl-cookieweb) - a web project template
-- [Feather](https://hg.sr.ht/~wnortje/feather), a template for web
- application development, shows a functioning Hello World app
- with an HTML page, a JSON API, a passing test suite, a Postgres DB
- and DB migrations. Uses Qlot, Buildapp, SystemD for deployment.
- [lisp-web-template-productlist](https://github.com/vindarel/lisp-web-template-productlist),
a simple project template with Hunchentoot, Easy-Routes, Djula and Bulma CSS.
- [lisp-web-live-reload-example](https://github.com/vindarel/lisp-web-live-reload-example/) -
diff --git a/content/tutorial/build.lisp b/content/tutorial/build.lisp
index 68c1a00..b8ba969 100644
--- a/content/tutorial/build.lisp
+++ b/content/tutorial/build.lisp
@@ -2,7 +2,14 @@
(ql:quickload "myproject")
-(sb-ext:save-lisp-and-die "myproject"
- :executable t
- :toplevel #'myproject::main
- :compression 9)
+(setf uiop:*image-entry-point* #'myproject::main)
+
+(uiop:dump-image "myproject" :executable t :compression 9)
+
+#|
+;; The same as:
+
+(sb-ext:save-lisp-and-die "myproject" :executable t :compression 9
+ :toplevel #'myproject::main)
+
+|#
diff --git a/content/tutorial/first-build.md b/content/tutorial/first-build.md
index 288de51..110d3bd 100644
--- a/content/tutorial/first-build.md
+++ b/content/tutorial/first-build.md
@@ -341,9 +341,12 @@ Building binaries with SBCL is done with the function
`sb-ext:save-lisp-and-die` (it lives in the `sb-ext` SBCL module, that
is available by default).
-Other implementations don't define the exact same function, that's why
-we need a compatibility layer, which is provided by ASDF. We show this
-method in the Cookbook and later in this guide.
+Other implementations don't define the exact same function, for
+instance on Clozure CL the function is `ccl:save-application`. That's
+why we'll want a compatibility layer to write a portable script across
+implementations. It is as always provided by ASDF with
+`uiop:dump-image` and also with a system declaration in the .asd
+files.
SBCL binaries are portable from and to the same operating system:
build on GNU/Linux, run on GNU/Linux. Or build on a CI system on the 3
diff --git a/docs/404.html b/docs/404.html
index 4c303ad..272602c 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -1,2 +1,2 @@
Whoops. Looks like this page doesn't exist ¯\_(ツ)_/¯.
Whoops. Looks like this page doesn't exist ¯\_(ツ)_/¯.