summarylogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.SRCINFO19
-rw-r--r--PKGBUILD34
-rw-r--r--Tutorial_01_Intro.md55
-rw-r--r--Tutorial_02_CutelystBasics.md279
-rw-r--r--Tutorial_03_MoreCutelystBasics.md588
-rw-r--r--Tutorial_04_BasicCRUD.md583
-rw-r--r--Tutorial_05_Authentication.md366
-rwxr-xr-xproc.sh13
8 files changed, 27 insertions, 1910 deletions
diff --git a/.SRCINFO b/.SRCINFO
index fd11e1fc6b32..d92d0d7950ab 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,22 +1,17 @@
pkgbase = cutelyst-tutorial-git
pkgdesc = The Wiki tutorial from Github as a local copy
- pkgver = 2.0.0
- pkgrel = 1
+ pkgver = 3.0.0
+ pkgrel = 2
url = http://cutelyst.org
arch = any
license = LGPL2.1
+ makedepends = git
makedepends = discount
- source = Tutorial_01_Intro.md
- source = Tutorial_02_CutelystBasics.md
- source = Tutorial_03_MoreCutelystBasics.md
- source = Tutorial_04_BasicCRUD.md
- source = Tutorial_05_Authentication.md
+ source = git+https://github.com/cutelyst/cutelyst.wiki.git
source = default.css
- sha512sums = 5853c75e508bf0b77b9c7d31349c9ebfed5203bf22cdc43eaefa0d67dc1861dd3f14dad3d1ba44079aedcd0155c159839e908cef27931031c71747b3dc7b49d2
- sha512sums = 28a469412467f015558487e2d9885e6e6a457fc490c78e06f36b78f058c8c516a346625c13f1a727126599999147ef9c92c234ad65835cfba91f027c51869a64
- sha512sums = 2cb5564f3f7bc3d78e29c920b1638d857892f38e24ba39f1c4c11cab95b4b32dd5a77f1b614c57e0b484cf086c8a31f4abd348662bb8953457b219b9e71220bb
- sha512sums = 2208df26239e7ab11b4ef31e5b6d4fbb4095ec07c1655bde00a3ac9e94d847609b8c25e0016be16341197093d9f00f2b9cd947e65a93569c8b60e29200b4900f
- sha512sums = cf0c683991c71e7b29a78f48a15f7514e282612221cc8ad31d5129f67df5d007eedf32442d6b5b1c402c949761ddbc4f6b08ddc37829245ff52731bb0e1617a8
+ source = proc.sh
+ sha512sums = SKIP
sha512sums = 6f5d6f353d66f785ada9c5060c01d2fec7a2b4dba642d81da59e2f0021351129569b20e741cc0b7f9b16abf7ae6447b1617b51054dfbe5e08fada7af1d529451
+ sha512sums = f9934c57e4b9344efabb209f174cdde76dc45fc24601c8d23390130ab749743d87f8650846c4172728546fdeb59890f589f14d4222f546da3c79773bdf80940b
pkgname = cutelyst-tutorial-git
diff --git a/PKGBUILD b/PKGBUILD
index 89b2429ff82d..2f04dee7f0a1 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -1,37 +1,31 @@
# Maintainer: Simon Wilper <sxw@chronowerks.de>
pkgname=('cutelyst-tutorial-git')
-pkgver=2.0.0
-pkgrel=1
+pkgver=3.0.0
+pkgrel=2
pkgdesc="The Wiki tutorial from Github as a local copy"
arch=('any')
url="http://cutelyst.org"
license=('LGPL2.1')
-makedepends=('discount')
+makedepends=('git' 'discount')
source=(
- 'Tutorial_01_Intro.md'
- 'Tutorial_02_CutelystBasics.md'
- 'Tutorial_03_MoreCutelystBasics.md'
- 'Tutorial_04_BasicCRUD.md'
- 'Tutorial_05_Authentication.md'
+ 'git+https://github.com/cutelyst/cutelyst.wiki.git'
'default.css'
+ 'proc.sh'
)
+sha512sums=('SKIP'
+ '6f5d6f353d66f785ada9c5060c01d2fec7a2b4dba642d81da59e2f0021351129569b20e741cc0b7f9b16abf7ae6447b1617b51054dfbe5e08fada7af1d529451'
+ 'f9934c57e4b9344efabb209f174cdde76dc45fc24601c8d23390130ab749743d87f8650846c4172728546fdeb59890f589f14d4222f546da3c79773bdf80940b')
build() {
- cd $startdir
- ./proc.sh out
+ cd ${srcdir}/cutelyst.wiki
+ ../proc.sh html
}
package() {
- cd $startdir
+ cd ${srcdir}/cutelyst.wiki/html
local t=usr/share/doc/cutelyst/tutorial
- install -d -m755 ${pkgdir}/$t/
- install -m644 out/*.html ${pkgdir}/$t/
- install -m644 default.css ${pkgdir}/$t/
+ install -d -m755 ${pkgdir}/$t
+ install -m644 *.html ${pkgdir}/$t
+ install -m644 ${startdir}/default.css ${pkgdir}/$t
}
-sha512sums=('5853c75e508bf0b77b9c7d31349c9ebfed5203bf22cdc43eaefa0d67dc1861dd3f14dad3d1ba44079aedcd0155c159839e908cef27931031c71747b3dc7b49d2'
- '28a469412467f015558487e2d9885e6e6a457fc490c78e06f36b78f058c8c516a346625c13f1a727126599999147ef9c92c234ad65835cfba91f027c51869a64'
- '2cb5564f3f7bc3d78e29c920b1638d857892f38e24ba39f1c4c11cab95b4b32dd5a77f1b614c57e0b484cf086c8a31f4abd348662bb8953457b219b9e71220bb'
- '2208df26239e7ab11b4ef31e5b6d4fbb4095ec07c1655bde00a3ac9e94d847609b8c25e0016be16341197093d9f00f2b9cd947e65a93569c8b60e29200b4900f'
- 'cf0c683991c71e7b29a78f48a15f7514e282612221cc8ad31d5129f67df5d007eedf32442d6b5b1c402c949761ddbc4f6b08ddc37829245ff52731bb0e1617a8'
- '6f5d6f353d66f785ada9c5060c01d2fec7a2b4dba642d81da59e2f0021351129569b20e741cc0b7f9b16abf7ae6447b1617b51054dfbe5e08fada7af1d529451')
diff --git a/Tutorial_01_Intro.md b/Tutorial_01_Intro.md
deleted file mode 100644
index 328809fa9883..000000000000
--- a/Tutorial_01_Intro.md
+++ /dev/null
@@ -1,55 +0,0 @@
-### OVERVIEW
-1. [Introduction](Tutorial_01_Intro)
-2. [Cutelyst Basics](Tutorial_02_CutelystBasics)
-3. [More Cutelyst Basics](Tutorial_03_MoreCutelystBasics)
-4. [Basic CRUD](Tutorial_04_BasicCRUD)
-5. [Authentication](Tutorial_05_Authentication)
-
-### INTRODUCTION
-
-This tutorial is an adaptation of the great Catalyst Tutorial to match what we currently have in Cutelyst.
-
-### DESCRIPTION
-
-This tutorial provides a multi-part introduction to the Cutelyst Web Framework. It seeks to provide a rapid overview of many of its most commonly used features. The focus is on the real-world best practices required in the construction of nearly all Cutelyst applications.
-
-Although the primary target of the tutorial is users new to the Cutelyst framework, experienced users may wish to review specific sections (for example, how to add authentication and authorization to an existing application).
-
-[Download the tutorial source code](https://github.com/cutelyst/cutelyst-tutorial)
-
-These reference implementations are provided so that when you follow the tutorial, you can use the code to ensure that your system is set up correctly, and that you have not inadvertently made any typographic errors, or accidentally skipped part of the tutorial.
-
-> NOTE: Cutelyst can run on any OS supported by Qt, you only need to pay attention to minimum Qt version required. It should make little or no difference to Cutelyst's operation, but this tutorial has been written using the Debian-based Tanglu OS.
-
-#### Subjects covered by the tutorial include:
-
-* A simple application that lists and adds books.
-* How to write CRUD (Create, Read, Update, and Delete) operations in Cutelyst.
-* Authentication ("auth").
-* Role-based authorization ("authz").
-* Attempts to provide an example showing current Cutelyst practices.
-* The use of Grantlee Template (Django).
-* Useful techniques for troubleshooting and debugging Cutelyst applications.
-* The use of SQLite as a database.
-
-This tutorial makes the learning process its main priority. For example, the level of comments in the code found here would likely be considered excessive in a "normal project." Because of their contextual value, this tutorial will generally favor inline comments over a separate discussion in the text. It also deliberately tries to demonstrate multiple approaches to various features (in general, you should try to be as consistent as possible with your own production code).
-
-Furthermore, this tutorial tries to minimize the number of controllers, templates, and database tables. Although this does result in things being a bit contrived at times, the concepts should be applicable to more complex environments.
-
-### VERSIONS AND CONVENTIONS USED IN THIS TUTORIAL
-
-This tutorial was built using the following resources. Please note that you may need to make adjustments for different environments and versions:
-
-* Ubuntu 17.10
-* Cutelyst 2.1.0
-* Qt 5.6
-* Grantlee 5.0
-* SQLite 3
-
-> uWSGI can also be used by replacing `cutelyst-wsgi2` with `uwsgi` and loading the cutelyst plugin, Cutelyst-WSGI supports HTTP, HTTPS and FastCGI while being faster, using less memory, and supporting HTTP keep-alive and pipelining), which is enough for production and development even on embedded hardware.
-
-### DATABASES
-
-This tutorial will primarily focus on SQLite because of its simplicity of installation and use; however, modifications in the script required to support MySQL and PostgreSQL will be presented in the Appendix.
-
-You can jump to the next chapter of the tutorial here: [Cutelyst Basics](Tutorial_02_CutelystBasics) \ No newline at end of file
diff --git a/Tutorial_02_CutelystBasics.md b/Tutorial_02_CutelystBasics.md
deleted file mode 100644
index 235428c3c0a2..000000000000
--- a/Tutorial_02_CutelystBasics.md
+++ /dev/null
@@ -1,279 +0,0 @@
-### OVERVIEW
-1. [Introduction](Tutorial_01_Intro)
-2. [Cutelyst Basics](Tutorial_02_CutelystBasics)
-3. [More Cutelyst Basics](Tutorial_03_MoreCutelystBasics)
-4. [Basic CRUD](Tutorial_04_BasicCRUD)
-5. [Authentication](Tutorial_05_Authentication)
-
-### DESCRIPTION
-
-In this chapter of the tutorial, we will create a very basic Cutelyst web application, demonstrating a number of powerful capabilities, such as:
-
-#### Developer Helper Command
-Cutelyst developer helper command that can be used to rapidly bootstrap the skeletal structure of an application.
-
-**MVC**
-Model/View/Controller (MVC) provides an architecture that facilitates a clean "separation of control" between the different portions of your application. Given that many other documents cover this subject in detail, MVC will not be discussed in depth here. In short:
-
-**Model**
-The model usually represents a data store. In most applications, the model equates to the objects that are created from and saved to your SQL database. Currently Cutelyst doesn't have a clear definition to models, QAbstractItemModel might end up being used to fill this MVC gap.
-
-**View**
-The view takes stash / model objects and renders them into something for the end user to look at. Normally this involves a template-generation tool that creates HTML for the user's web browser, but it could easily be code that generates other forms such as PDF documents, e-mails, spreadsheets, or even "behind the scenes" formats such as XML and JSON.
-
-**Controller**
-As suggested by its name, the controller takes user requests and routes them to the necessary model and view.
-
-
-### CREATE A CUTELYST PROJECT
-
-Cutelyst provides a number of options in it's developer command that can be used to quickly flesh out the basic structure of your application.
-
-> If you are using QtCreator it's easier to setup the Cutelyst QtCreator assistant, and create the project there. The command line tool will be used in this tutorial as not everyone like QtCreator (I love it!).
-
-Let's create our first Cutelyst application with cutelyst command to initialize an application called Hello:
-
-```bash
-$ cutelyst2 --create-app Hello
- created "Hello"
- created "Hello/src"
- created "Hello/root"
- ...
-```
-
-The cutelyst2 helper command will display the names of the directories and files it creates:
-
- CMakeLists.txt # CMake file to build application
- src # Application main code directory
- CMakeLists.txt # CMake file with the settings to find your code
- root.cpp # Sample Root controller implementation
- root.h # Sample Root controller header
- hello.cpp # Base application implementation
- hello.h # Base application header
- root # Equiv of htdocs, dir for templates, css, javascript
- static # Directory for static files
-
-Since Cutelyst is build with C++ and so is your application, you need to first
-compile it. To do so make sure you run the following commands:
-
-```bash
-$ cd Hello # Get into the application root directory
-$ cd build # Get into the out of tree build directory
-$ cmake .. # Prepare the project for compilation
-$ make # Compile the application
-```
-
-If everything went well and although it's too early for any significant celebration, we almost have a functioning application. We can use the `cutelyst` server to start the application and view the default Cutelyst page in your browser.
-
-Run the following command to start up the application web server:
-
->Note: The "-r" argument enables reloading your application when you have rebuilt your code so you don't have to stop and start the server every time you make a change and rebuild. Most of the rest of the tutorial will assume that you are using "-r" when you start the server, but feel free to manually start and stop it (use Ctrl-C to breakout of the dev server) if you prefer.
-
->Note: You can omit the application suffix .so, .dll, .dylib as it will automatically look for the file with the appropriate suffix.
-
- $ cutelyst2 -r --server --app-file src/libHello -- --chdir ..
- cutelyst.uwsgi[debug] Cutelyst loading application: /home/daniel/code/Hello/build/src/libHello.so
- cutelyst.uwsgi[debug] Loaded application: "Hello"
- cutelyst.dispatcher[debug]
- Loaded Private actions:
- .--------------+-------+-------------.
- | Private | Class | Method |
- .--------------+-------+-------------.
- | /End | Root | End |
- | /defaultPage | Root | defaultPage |
- | /index | Root | index |
- .--------------+-------+-------------.
-
- Loaded Path actions:
- .------+--------------.
- | Path | Private |
- .------+--------------.
- | /... | /defaultPage |
- | / | /index |
- .------+--------------.
-
-Point your web browser to http://localhost:3000 (substituting a different hostname or IP address as appropriate) and you should be greeted by the Cutelyst welcome screen (if you get some other welcome screen or an "Index" screen, you probably forgot to specify port 3000 in your URL). Information similar to the following should be appended to the logging output of the development server:
-
- cutelyst.request: "GET" request for "/" from "::1"
- cutelyst.dispatcher: Path is "/"
- cutelyst.stats: Response Code: 200; Content-Type: text/html; charset=utf-8; Content-Length: 20
- cutelyst.stats: Request took: 0.000001s (1000000.000/s)
-
-Note: Press Ctrl-C to break out of the development server if necessary.
-
-### HELLO WORLD
-
-The Simplest Way
-
-The Root.h controller is a place to put global actions that usually execute on the root URL. Open the src/Root.h and src/Root.cpp files in your editor. You will see the "index" method, which is responsible for displaying the welcome screen that you just saw in your browser.
-
-```c++
-class Root : public Controller
-{
- ...
- // Root.h
- C_ATTR(index, :Path :AutoArgs)
- void index(Context *c);
- ...
-};
-```
-
-```c++
-// Root.cpp
-void Root::index(Context *c)
-{
- c->response()->body() = "Welcome to Cutelyst!";
-}
-```
-
-Later on you'll want to change that to something more reasonable, such as a "404" message or a redirect, but for now just leave it alone.
-
-The "c" here refers to the Cutelyst::Context, which is used to access the Cutelyst application. In addition to many other things, the Cutelyst context provides access to "response" and "request" objects. (See Cutelyst::Context, Cutelyst::Response, and Cutelyst::Request)
-
-c->response()->body() sets the HTTP response (see Cutelyst::Response) with the provided string.
-
-The `:Path :AutoArgs` in the `C_ATTR` macro are attributes which determine which URLs will be dispatched to this method.
-
-Some MVC frameworks handle dispatching in a central place. Cutelyst, by policy, prefers to handle URL dispatching with attributes on controller methods. There is a lot of flexibility in specifying which URLs to match. This particular method will match all URLs, because it doesn't specify the path (nothing comes after "Path"), but will only accept a URL without any args because of the ":AutoArgs" attribute that will count how many QString parameters exist in the method's signature.
-
-The default is to map URLs to controller names, it is simple to create hierarchical structures in Cutelyst by using the C_NAMESPACE macro.
-
-While you leave the `cutelyst -r` command running the application server in one window (don't forget the "-r" option!), open another window and add the following subroutine to your src/Root.h file:
-
-```c++
-class Root : public Controller
-{
- ...
- C_ATTR(hello, :Global :AutoArgs)
- void hello(Context *c) {
- c->response()->body() = "Hello, World!";
- }
- ...
-};
-```
-
-NOTE: It's recommended to keep the implementation of the methods in the cpp file, this avoids recompiling other parts of your code that include this header, we keep it in the header here for brevity.
-
-Run make again and notice in the window running the application Server that you should get output similar to the following:
-
- Attempting to restart the server
- ...
- Loaded Private actions:
- .--------------+-------+-------------.
- | Private | Class | Method |
- .--------------+-------+-------------.
- | /End | Root | End |
- | /defaultPage | Root | defaultPage |
- | /hello | Root | hello |
- | /index | Root | index |
- .--------------+-------+-------------.
- ...
-The development server noticed the change in `...libHello.so` and automatically restarted itself.
-
-Go to http://localhost:3000/hello to see "Hello, World!". Also notice that the newly defined 'hello' action is listed under "Loaded Private actions" in the development server debug output.
-
-### Hello, World! Using a View and a Template
-
-In the Cutelyst world a "View" itself is not a page of XHTML or a template designed to present a page to a browser. Rather, it is the module that determines the type of view -- HTML, PDF, XML, etc. For the thing that generates the content of that view (such as a Grantlee template file), the actual templates go under the "root" directory.
-
-To create a Grantlee view, add to src/hello.cpp:
-
-```c++
-...
-#include <Cutelyst/Plugins/View/Grantlee/grantleeview.h>
-...
-bool Hello::init()
-{
- ...
- new GrantleeView(this);
- ...
-}
-```
-
-And link to it, src/CMakeLists.txt:
-
-```cmake
-target_link_libraries(Hello
- ...
- Cutelyst::View::Grantlee # Add this line
- ...
-}
-```
-
-Now that the View exists, Cutelyst will be able to use it to display the view templates using the "process" method that it inherits from the Cutelyst::View class.
-
-Grantlee is a very full-featured template facility, with excellent documentation at http://www.grantlee.org, but since this is not a Grantlee tutorial, we'll stick to only basic usage here (and explore some of the more common Grantlee features in later chapters of the tutorial).
-
-Create a root/hello.html template file (put it in the root under the Hello directory that is the base of your application). Here is a simple sample:
-
-```html
-<p>
- This is a Grantlee view template, called '{{ template }}'.
-</p>
-```
-{{ and }} are markers for the Grantlee parts of the template. Inside you can access Cutelyst variables and classes, and use Grantlee directives. In this case, we're using a special Cutelyst variable that defines the name of the template file (hello.html). The rest of the template is normal HTML.
-
-Change the hello method in src/root.h to the following:
-
-```c++
-C_ATTR(hello, :Global)
-void hello(Context *c) {
- c->setStash("template", "hello.html");
-}
-```
-
-This time, instead of doing c->response()->body(), you are setting the value of the "template" hash key in the Cutelyst "stash", an area for putting information to share with other parts of your application. The "template" key determines which template will be displayed at the end of the request cycle. Cutelyst controllers can have an "End" action, which is called after all methods, and together with RenderView action class causes the nameless view to be rendered (unless there's a body explicitly set with c->response()->body() or a named view set with c->setView()). So your template will be magically displayed at the end of your method.
-
-After saving the file and issuing make, the development server should automatically restart (again, the tutorial is written to assume that you are using the "-r" option -- manually restart it if you aren't), and look at http://localhost:3000/hello in your web browser again. You should see the template that you just created.
-
-### CREATE A SIMPLE CONTROLLER AND AN ACTION
-
-Create a controller named "Site" by executing the cutelyst command:
-
- $ cutelyst2 --controller Site
-This will create a src/site.cpp and src/site.h files. If you bring site.h up in your editor, you can see that there's not much there to see.
-
-We now need to register this controller (unfortunately C++/Qt's introspection has it's limitations), so in src/hello.cpp add:
-
-```c++
-....
-#include "site.h"
-....
-bool Hello::init()
-{
- ....
- new Site(this);
- ....
-}
-```
-
-In src/site.h, add the following method:
-
-```c++
- C_ATTR(test, :Local)
- void test(Context *c) {
- c->stash({
- {"username", "John"},
- {"template", "site/test.html"}
- });
- }
-```
-
-Notice the "Local" attribute on the test method. This will cause the test action (now that we have assigned an "action type" to the method it appears as a "controller action" to Cutelyst) to be executed on the "controller/method" URL, or, in this case, "site/test". We will see additional information on controller actions throughout the rest of the tutorial, but if you are curious take a look at "Actions" in Cutelyst::Action API.
-
-It's not actually necessary to set the template value as we do here. By default Grantlee will attempt to render a template that follows the naming pattern "controller/method.html", and we're following that pattern here. However, in other situations you will need to specify the template (such as if you've "forwarded" to the method, or if it doesn't follow the default naming convention).
-
-We've also put the variable "username" into the stash, for use in the template.
-
-Make a subdirectory "site" in the "root" directory.
-
- $ mkdir root/site
-Create a new template file in that directory named root/site/test.html and include a line like:
-
-```html
-<p>Hello, {{ username }}!</p>
-```
-
-Rebuild, and once the server automatically restarts, notice in the server output that /site/test is listed in the Loaded Path actions. Go to http://localhost:3000/site/test in your browser and you should see your test.html file displayed, including the name "John" that you set in the controller.
-
-You can jump to the next chapter of the tutorial here: [More Cutelyst Basics](Tutorial_03_MoreCutelystBasics)
diff --git a/Tutorial_03_MoreCutelystBasics.md b/Tutorial_03_MoreCutelystBasics.md
deleted file mode 100644
index 001ccf1c293a..000000000000
--- a/Tutorial_03_MoreCutelystBasics.md
+++ /dev/null
@@ -1,588 +0,0 @@
-### OVERVIEW
-1. [Introduction](Tutorial_01_Intro)
-2. [Cutelyst Basics](Tutorial_02_CutelystBasics)
-3. [More Cutelyst Basics](Tutorial_03_MoreCutelystBasics)
-4. [Basic CRUD](Tutorial_04_BasicCRUD)
-5. [Authentication](Tutorial_05_Authentication)
-
-### DESCRIPTION
-
-This chapter of the tutorial builds on the work done in Chapter 2 to explore some features that are more typical of "real world" web applications. From this chapter of the tutorial onward, we will be building a simple book database application. Although the application will be too limited to be of use to anyone, it should provide a basic environment where we can explore a variety of features used in virtually all web applications.
-
-### CREATE A NEW APPLICATION
-
-The remainder of the tutorial will build an application called MyApp. First use the Cutelyst cutelyst command to initialize the framework for the MyApp application (make sure you aren't still inside the directory of the Hello application from the previous chapter of the tutorial or in a directory that already has a "MyApp" subdirectory):
-
-```bash
-$ cutelyst2 --create-app MyApp
- created "MyApp"
- created "MyApp/CMakeLists.txt"
- created "MyApp/build"
- created "MyApp/root"
- created "MyApp/src"
- ...
-```
-
-Change to application directory, then build directory and Run "cmake .." to make sure your install is complete
-
-And change the "MyApp" directory the helper created:
-
-```bash
-$ cd MyApp
-```
-
-This creates a similar skeletal structure to what we saw in Chapter 2 of the tutorial, except with MyApp and myapp substituted for Hello and hello.
-
-Cutelyst used to create an application with `Cutelyst::StaticSimple` plugin enabled by default, StaticSimple provides an easy way to serve static content, such as images and CSS files, from within your application.
-
-However it's much better to use cutelyst (or uwsgi) `--static-map` or serve them with your front web server. Which is why this plugin doesn't come enabled by default anymore.
-
-When adding new plugins make sure you pass the Cutelyst::Application as their parent so they get automatically registered, as well as adding it's new dependency within the CMakeLists.txt file.
-
-### CREATE A CUTELYST CONTROLLER
-
-As discussed earlier, controllers are where you write methods that interact with user input. Typically, controller methods respond to GET and POST requests from the user's web browser.
-
-Use the Cutelyst cutelyst command to add a controller for book-related actions:
-
-```
-$ cutelyst2 --controller Books
- created "/home/daniel/code/MyApp/src/books.h"
- created "/home/daniel/code/MyApp/src/books.cpp"
-Now, on your application class include and instantiate the controller.
-```
-
-To register and instantiate the controller, add to your src/myapp.cpp:
-
-```c++
-...
-#include "books.h"
-...
-bool MyApp::init()
-{
- ....
- new Books(this);
- ....
-}
-```
-
-Then edit src/books.h and add the method "list" to the controller:
-
-```c++
-class Books : public Controller
-{
-...
-/**
- * Fetch all book objects and pass to books/list.html in stash to be displayed
- */
-C_ATTR(list, :Local)
-void list(Context *c);
-};
-```
-
-And in src/books.cpp the implementation:
-
-```c++
-void Books::list(Context *c)
-{
- // c is the Cutelyst::Context that's used to 'glue together'
- // the various components that make up the application
-
- // Retrieve all of the book records as book model objects and store in the
- // stash where they can be accessed by the Grantlee template
- // c->setStash("books", sql result);
- // But, for now, use this code until we create the model later
- c->setStash("books", "");
-
- // Set the Grantlee template to use. You will almost always want to do this
- // in your action methods (action methods respond to user input in
- // your controllers).
- c->setStash("template", "books/list.html");
-}
-```
-Here we see "Cutelyst Context object", which is automatically passed as the first argument to all Cutelyst action methods. It is used to pass information between components and provide access to Cutelyst and plugin functionality.
-
-Cutelyst Controller actions are regular C++ class methods, but they make use of attributes (the ":Local" inside the C_ATTR macro which also makes the method invokable) to provide additional information to the Cutelyst dispatcher logic (note that there can be an optional space between the colon and the attribute name; you will see attributes written both ways). Most Cutelyst Controllers use one of five action types:
-
-**:Private** -- Use :Private (or place your method under private section) for methods that you want to make into an action, but you do not want Cutelyst to directly expose the method to your users. Cutelyst will not map :Private methods to a URI. Use them for various sorts of "special" methods (the Begin, Auto, etc. discussed below) or for methods you want to be able to forward or detach to. (If the method is a "plain old method" that you don't want to be an action at all, then just define the method without any attribute -- you can call it in your code, but the Cutelyst dispatcher will ignore it.
-There are five types of "special" built-in :Private actions: Begin, End, Default, Index, and Auto.
-
-With Begin, End, Default, Index private actions, only the most specific action of each type will be called. For example, if you define a Begin action in your controller it will override a Begin action in your application/root controller -- only the action in your controller will be called.
-Unlike the other actions where only a single method is called for each request, every Auto action along the chain of namespaces will be called. Each Auto action will be called from the application/root controller down through the most specific class.
-
-**:Path** -- :Path actions let you map a method to an explicit URI path. For example, ":Path('list')" in src/books.h would match on the URL http://localhost:3000/books/list, but ":Path('/list')" would match on http://localhost:3000/list (because of the leading slash). You can use :Args() to specify how many arguments an action should accept.
-
-**:Local** -- :Local is merely a shorthand for ":Path('_name_of_method_')". For example, these are equivalent: "C_ATTR(create_book, :Local) ... (Context*) {...}" and "C_ATTR(create_book, :Path("create_book")) ... {...}".
-
-**:Global** -- :Global is merely a shorthand for ":Path('/_name_of_method_')". For example, these are equivalent: "C_ATTR(create_book, :Global) ... {...}" and "C_ATTR(create_book, :Path("/create_book")) ... {...}".
-
-**:Chained** -- Newer Cutelyst applications tend to use the Chained dispatch form of action types because of its power and flexibility. It allows a series of controller methods to be automatically dispatched when servicing a single user request. See Cutelyst::Manual::Tutorial::04_BasicCRUD and Cutelyst::DispatchType::Chained for more information on chained actions.
-
-### CUTELYST VIEWS
-
-As mentioned in Chapter 2 of the tutorial, views are where you render output, typically for display in the user's web browser (but can generate other types of output such as PDF or JSON). The code in src/myapp.cpp selects the type of view to use, with the actual rendering template found in the root directory. As with virtually every aspect of Cutelyst, options abound when it comes to the specific view technology you adopt inside your application. However, most Cutelyst applications use the Grantlee (for more information on TT, see http://www.grantlee.org). At the moment other somewhat popular view technology is ClearSilver (http://www.clearsilver.net/).
-
-#### Register a Cutelyst View
-
-It is now up to you to decide how you want to structure your view layout. For the tutorial, we will start with a very simple Grantlee template to initially demonstrate the concepts, but quickly migrate to a more typical "wrapper page" type of configuration (where the "wrapper" controls the overall "look and feel" of your site from a single file or set of files). Edit src/myapp.cpp:
-
-```c++
-....
-#include <Cutelyst/Plugins/View/Grantlee/grantleeview.h>
-....
-bool MyApp::init()
-{
- ....
- auto view = new GrantleeView(this);
- view->setIncludePaths({ pathTo("root/src") });
- ....
-}
-```
-
-And link to Grantlee, adding to src/CMakeLists.txt:
-
-```cmake
-target_link_libraries(MyApp
- ...
- Cutelyst::View::Grantlee # Add this line
- ...
-}
-```
-
-This changes the base directory for your template files from root to root/src.
-
-Please stick with the settings above for the duration of the tutorial, but feel free to use whatever options you desire in your applications.
-
-Note: We will use root/src as the base directory for our template files, with a full naming convention of root/src/_controller_name_/_action_name_.html. Another popular option is to use root/ as the base (with a full filename pattern of root/_controller_name_/_action_name_.html).
-
-#### Create a Grantlee Template Page
-
-First create a directory for book-related templates:
-
-```bash
-$ mkdir -p root/src/books
-```
-
-Then create root/src/books/list.html in your editor and enter:
-
-```html
-{% comment %}This is a Grantlee comment.{% endcomment %}
-
-{% comment %}Some basic HTML with a loop to display books{% endcomment %}
-<table>
-<tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr>
-{% comment %}Display each book in a table row{% endcomment %}
-{% for book in books %}
- <tr>
- <td>{{ book.title }}</td>
- <td>{{ book.rating }}</td>
- <td></td>
- </tr>
-{% endfor %}
-</table>
-```
-
-As indicated by the inline comments above, the **for** loop iterates through each book model object and prints the title and rating fields.
-
-The {% and %} tags are used to delimit Grantlee code. Grantlee supports a wide variety of directives for "calling" other files, looping, conditional logic, etc. In general, Grantlee simplifies the usual range of Qt introspection properties down to the single dot (".") operator. This applies to properties, hash lookups, and list index values.
-
-TIP: While you can build all sorts of complex logic into your Grantlee templates, you should in general keep the "code" part of your templates as simple as possible.
-
-#### Test Run The Application
-
-To test your work so far, compile then start the development server:
-
-```bash
-$ make && cutelyst2 -r --server --app-file src/libMyApp -- --chdir ..
-```
-
-Then point your browser to http://localhost:3000 and you should still get the Cutelyst welcome page. Next, change the URL in your browser to http://localhost:3000/books/list. If you have everything working so far, you should see a web page that displays nothing other than our column headers for "Title", "Rating", and "Author(s)" -- we will not see any books until we get the database and model working below.
-
-If you run into problems getting your application to run correctly, it might be helpful to refer to some of the debugging techniques covered in the Debugging chapter of the tutorial.
-
-### CREATE A SQLITE DATABASE
-
-In this step, we make a text file with the required SQL commands to create a database table and load some sample data. We will use SQLite (http://www.sqlite.org), a popular database that is lightweight and easy to use. Be sure to get at least version 3. Open myapp01.sql in your editor and enter:
-
-```sql
---
--- Create a very simple database to hold book and author information
---
-PRAGMA foreign_keys = ON;
-CREATE TABLE book (
- id INTEGER PRIMARY KEY,
- title TEXT ,
- rating INTEGER
-);
--- 'book_author' is a many-to-many join table between books & authors
-CREATE TABLE book_author (
- book_id INTEGER REFERENCES book(id) ON DELETE CASCADE ON UPDATE CASCADE,
- author_id INTEGER REFERENCES author(id) ON DELETE CASCADE ON UPDATE CASCADE,
- PRIMARY KEY (book_id, author_id)
-);
-CREATE TABLE author (
- id INTEGER PRIMARY KEY,
- first_name TEXT,
- last_name TEXT
-);
----
---- Load some sample data
----
-INSERT INTO book VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
-INSERT INTO book VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
-INSERT INTO book VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
-INSERT INTO book VALUES (4, 'Perl Cookbook', 5);
-INSERT INTO book VALUES (5, 'Designing with Web Standards', 5);
-INSERT INTO author VALUES (1, 'Greg', 'Bastien');
-INSERT INTO author VALUES (2, 'Sara', 'Nasseh');
-INSERT INTO author VALUES (3, 'Christian', 'Degu');
-INSERT INTO author VALUES (4, 'Richard', 'Stevens');
-INSERT INTO author VALUES (5, 'Douglas', 'Comer');
-INSERT INTO author VALUES (6, 'Tom', 'Christiansen');
-INSERT INTO author VALUES (7, 'Nathan', 'Torkington');
-INSERT INTO author VALUES (8, 'Jeffrey', 'Zeldman');
-INSERT INTO book_author VALUES (1, 1);
-INSERT INTO book_author VALUES (1, 2);
-INSERT INTO book_author VALUES (1, 3);
-INSERT INTO book_author VALUES (2, 4);
-INSERT INTO book_author VALUES (3, 5);
-INSERT INTO book_author VALUES (4, 6);
-INSERT INTO book_author VALUES (4, 7);
-INSERT INTO book_author VALUES (5, 8);
-```
-
-Then use the following command to build a myapp.db SQLite database:
-
-```bash
-$ sqlite3 myapp.db < myapp01.sql
-```
-
-If you need to create the database more than once, you probably want to issue the rm myapp.db command to delete the database before you use the sqlite3 myapp.db < myapp01.sql command.
-
-Once the myapp.db database file has been created and initialized, you can use the SQLite command line environment to do a quick dump of the database contents:
-
-```
- $ sqlite3 myapp.db
- SQLite version 3.7.3
- Enter ".help" for instructions
- Enter SQL statements terminated with a ";"
- sqlite> select * from book;
- 1|CCSP SNRS Exam Certification Guide|5
- 2|TCP/IP Illustrated, Volume 1|5
- 3|Internetworking with TCP/IP Vol.1|4
- 4|Perl Cookbook|5
- 5|Designing with Web Standards|5
- sqlite> .q
- $
-```
-
-Or:
-
-```
- $ sqlite3 myapp.db "select * from book"
- 1|CCSP SNRS Exam Certification Guide|5
- 2|TCP/IP Illustrated, Volume 1|5
- 3|Internetworking with TCP/IP Vol.1|4
- 4|Perl Cookbook|5
- 5|Designing with Web Standards|5
-```
-
-As with most other SQL tools, if you are using the full "interactive" environment you need to terminate your SQL commands with a ";" (it's not required if you do a single SQL statement on the command line). Use ".q" to exit from SQLite from the SQLite interactive mode and return to your OS command prompt.
-
-For using other databases, such as PostgreSQL or MySQL, see Appendix 2.
-
-### DATABASE ACCESS WITH QtSql
-
-Cutelyst can be used in conjunction with ORMs such as QxORM (http://www.qxorm.com/) or ODB (http://codesynthesis.com/products/odb/), but for the time being we are going to use QtSql and write our own SQL statements. In future we might write appendixes for them, so for now make sure you have QtSql development packages as well as Qt Sqlite3 driver installed. Cutelyst::Sql::Utils will also be used to simplify Sql code.
-
-Before you continue, make sure your myapp.db database file is in the application's topmost directory.
-
-Now we are going to add the required code open the database connection:
-
-First change CMakeLists.txt to find QtSql:
-
-```diff
-- find_package(Qt5 COMPONENTS Core Network)
-+ find_package(Qt5 COMPONENTS Core Network Sql)
-```
-
-Then src/CMakeLists.txt to include and link to QtSql:
-
-```cmake
-target_link_libraries(MyApp
- ...
- Qt5::Sql # Add this line
- Cutelyst::Utils::Sql # Add this line
- ...
-}
-```
-
-With QtSql we can open a connection to the database once and reuse it for application's lifetime, but it's important to notice that you must not open it before forking a new process, otherwise all child process will share the same connection and the behavior is undefined.
-
-Add the virtual *postFork()* method to your main application class, there you can return false if your database fails to open:
-
-```C++
-// myapp.h
-public:
- ...
- virtual bool postFork() override;
-```
-
-```C++
-// myapp.cpp
-...
-#include <QtSql>
-#include <Cutelyst/Plugins/Utils/Sql>
-...
-bool MyApp::postFork()
-{
- QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", Sql::databaseNameThread("MyDB"));
- db.setDatabaseName("myapp.db");
- db.setConnectOptions("foreign_keys = ON");
- if (!db.open()) {
- qCritical() << "Failed to open database:" << db.lastError().text();
- return false;
- }
- return true;
-}
-```
-
-### ENABLE THE MODEL IN THE CONTROLLER
-
-For the time being QtSql or more specifically QSqlQuery doesn't provide any form of introspection, which makes it unusable for QML and Grantlee which require it, because of that we need to read all result and put it into introspectable objects (such as QVariantHash), for that we will use Cutelyst::Utils::Sql
-
-Open src/books.cpp and add the code to fetch the list of books:
-
-```diff
-+ #include <QtSql>
-+ #include <Cutelyst/Plugins/Utils/Sql>
-void Books::list(Context *c)
-{
-+ QSqlQuery query = CPreparedSqlQueryThreadForDB("SELECT * FROM book", "MyDB");
-+ if (query.exec()) {
-+ c->setStash("books", Sql::queryToHashList(query));
-+ }
-- c->setStash("books", "");
-}
-```
-
-#### Test Run The Application
-
-Rebuild the application and and see the server restarting.
-
-Some things you should note in the server output:
-
-To view the book list, change the URL in your browser to http://localhost:3000/books/list. You should get a list of the five books loaded by the myapp01.sql script above without any formatting. The rating for each book should appear on each row, but the "Author(s)" column will still be blank (we will fill that in later).
-
-You now have the beginnings of a simple but workable web application. Continue on to future sections and we will develop the application more fully.
-
-### CREATE A WRAPPER FOR THE VIEW
-
-When using Grantlee, you can (and should) create a wrapper that will literally wrap content around each of your templates. This is certainly useful as you have one main source for changing things that will appear across your entire site/application instead of having to edit many individual files.
-
-#### Configure the view in myapp.cpp For The Wrapper
-
-In order to create a wrapper, you must first edit your view object and tell it where to find your wrapper file.
-
-Edit your view in src/myapp.cpp and change it to match the following:
-
-```c++
-bool MyApp::init()
-{
- ...
- auto view = new GrantleeView(this);
- view->setIncludePaths({ pathTo("root/src") });
- ...
- view->setWrapper("wrapper.html"); // Add this line
- ...
-}
-```
-
-#### Create the Wrapper Template File and Stylesheet
-
-Next you need to set up your wrapper template. Basically, you'll want to take the overall layout of your site and put it into this file. For the tutorial, open root/src/wrapper.html and input the following:
-
-```html
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" [%#
- %]"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
-<title>{{ template }}</title>
-<link rel="stylesheet" href="/static/css/main.css" />
-</head>
-
-<body>
-<div id="outer">
-<div id="header">
- {% comment %} Insert the page title {% endcomment %}
- <h1>{{ site.title }}</h1>
-</div>
-
-<div id="bodyblock">
-<div id="menu">
- Navigation:
- <ul>
- <li><a href="/books/list">Home</a></li>
- <li><a href="/" title="Cutelyst Welcome Page">Welcome</a></li>
- </ul>
-</div><!-- end menu -->
-
-<div id="content">
- {% comment %} Status and error messages {% endcomment %}
- <span class="message">{{ status_msg }}</span>
- <span class="error">{{ error_msg }}</span>
- {% comment %} This is where Grantlee will stick all of your template's contents.{% endcomment %}
- {{ content }}
-</div><!-- end content -->
-</div><!-- end bodyblock -->
-
-<div id="footer">Copyright (c) your name goes here</div>
-</div><!-- end outer -->
-
-</body>
-</html>
-```
-
-Notice the status and error message sections in the code above:
-
-```html
- <span class="status">{{ status_msg }}</span>
- <span class="error">{{ error_msg }}</span>
-```
-
-If we set either message in the Cutelyst stash (e.g., c->setStash("status_msg", "Request was successful!") it will be displayed whenever any view used by that request is rendered. The message and error CSS styles can be customized to suit your needs in the root/static/css/main.css file we create below.
-
-Notes:
-
-The Cutelyst stash only lasts for a single HTTP request. If you need to retain information across requests you can use Cutelyst::Plugin::Session (we will use Cutelyst sessions in the Authentication chapter of the tutorial).
-Although it is beyond the scope of this tutorial, you may wish to use a JavaScript or AJAX tool such as jQuery (http://www.jquery.com) or Dojo (http://www.dojotoolkit.org).
-
-#### Create A Basic Stylesheet
-
-First create a central location for stylesheets under the static directory:
-
-```bash
-$ mkdir -p root/static/css
-```
-
-Then open the file root/static/css/main.css (the file referenced in the stylesheet href link of our wrapper above) and add the following content:
-
-```css
-#header {
- text-align: center;
-}
-#header h1 {
- margin: 0;
-}
-#header img {
- float: right;
-}
-#footer {
- text-align: center;
- font-style: italic;
- padding-top: 20px;
-}
-#menu {
- font-weight: bold;
- background-color: #ddd;
-}
-#menu ul {
- list-style: none;
- float: left;
- margin: 0;
- padding: 0 0 50% 5px;
- font-weight: normal;
- background-color: #ddd;
- width: 100px;
-}
-#content {
- margin-left: 120px;
-}
-.message {
- color: #390;
-}
-.error {
- color: #f00;
-}
-```
-
-You may wish to check out a "CSS Framework" like Emastic (http://code.google.com/p/emastic/) as a way to quickly provide lots of high-quality CSS functionality.
-
-#### Test Run The Application
-
-Rebuild and hit "Reload" in your web browser and you should now see a formatted version of our basic book list. (Again, the development server should have automatically restarted when you run make. If you are not using the "-r" option, you will need to hit Ctrl-C and manually restart it. Also note that the development server does NOT need to restart for changes to the Grantlee and static files we created and edited in the root directory -- those updates are handled on a per-request basis.)
-
-Although our wrapper and stylesheet are obviously very simple, you should see how it allows us to control the overall look of an entire website from two central files. To add new pages to the site, just provide a template that fills in the content section of our wrapper template -- the wrapper will provide the overall feel of the page.
-
-### OPTIONAL INFORMATION
-
-NOTE: The rest of this chapter of the tutorial is optional. You can skip to Chapter 4, Basic CRUD, if you wish.
-
-Using 'RenderView' for the Default View
-
-Once your controller logic has processed the request from a user, it forwards processing to your view in order to generate the appropriate response output. Cutelyst uses Cutelyst::Action::RenderView by default to automatically perform this operation. If you look in src/root.h, you should see the empty definition for the sub end method:
-
-```c++
-private:
- C_ATTR(End, :ActionClass("RenderView"))
- void End(Context *c) { Q_UNUSED(c); }
-```
-
-The following bullet points provide a quick overview of the RenderView process:
-
-Root class is designed to hold application-wide logic.
-At the end of a given user request, Cutelyst will call the most specific *End* method that's appropriate. For example, if the controller for a request has an *End* method defined, it will be called. However, if the controller does not define a controller-specific *End* method, the "global" *End* method in Root.h will be called.
-Because the definition includes an ActionClass attribute, the Cutelyst::Action::RenderView logic will be executed after any code inside the definition of *End* is run. See Cutelyst::Manual::Actions for more information on ActionClass.
-Because *End* is empty, this effectively just runs the default logic in RenderView. However, you can easily extend the RenderView logic by adding your own code inside the empty method body ({}) created by the Cutelyst Helpers when we first ran the cutelst command to initialize our application.
-
-#### Calling the View Renderer directly
-
-When using the *End* action there is no easy way to call different view renderers. For instance you want one action to be rendered through grantlee and another in the same class through the JSON renderer. In this case you need to register both views in the `init()` method but you need to give them unique names passed as the second parameter:
-
-```
-bool MyApp::init() {
- new GrantleeView(this, "grantlee_view");
- new ViewJson(this, "json_view");
-
- return true;
-}
-```
-
-In the header file you can now decide which renderer to use by setting the `:View` C_ATTR parameter:
-
-```
-C_ATTR(index, :Path :Args(0) :ActionClass("RenderView") :View("grantlee_view"))
-void index(Context *c);
-
-C_ATTR(entries, :Local :Args(0) :ActionClass("RenderView") :View("json_view"))
-void entries(Context *c);
-```
-
-#### Using The Default Template Name
-
-By default, Cutelyst::View::Grantlee will look for a template that uses the same name as your controller action, allowing you to save the step of manually specifying the template name in each action. For example, this would allow us to remove the c->setStash("template", "books/list.html"); line of our list action in the Books controller. Open src/books.cpp in your editor and comment out this line to match the following:
-
-```diff
-- c->setStash("template", "books/list.html");
-+ // c->setStash("template", "books/list.html");
-```
-
-You should now be able to access the http://localhost:3000/books/list URL as before.
-
-NOTE: If you use the default template technique, you will not be able to use either the c->forward or the c->detach mechanisms (these are discussed in Chapter 2 and Chapter 9 of the Tutorial).
-
-IMPORTANT: Make sure that you do not skip the following section before continuing to the next chapter 4 Basic CRUD.
-
-#### Return To A Manually Specified Template
-
-In order to be able to use c->forward() and c->detach() later in the tutorial, you should remove the comment from the statement in the method list in MyApp/src/books.cpp:
-
-```diff
-- // c->setStash("template", "books/list.html");
-+ c->setStash("template", "books/list.html");
-```
-
-Check the http://localhost:3000/books/list URL in your browser. It should look the same manner as with earlier sections.
-
-You can jump to the next chapter of the tutorial here: [Basic CRUD](Tutorial_04_BasicCRUD)
diff --git a/Tutorial_04_BasicCRUD.md b/Tutorial_04_BasicCRUD.md
deleted file mode 100644
index dae0fd5c553b..000000000000
--- a/Tutorial_04_BasicCRUD.md
+++ /dev/null
@@ -1,583 +0,0 @@
-### OVERVIEW
-
-1. [Introduction](Tutorial_01_Intro)
-2. [Cutelyst Basics](Tutorial_02_CutelystBasics)
-3. [More Cutelyst Basics](Tutorial_03_MoreCutelystBasics)
-4. [Basic CRUD](Tutorial_04_BasicCRUD)
-5. [Authentication](Tutorial_05_Authentication)
-
-### INTRODUCTION
-
-This chapter of the tutorial builds on the fairly primitive application created in Chapter 3 to add basic support for Create, Read, Update, and Delete (CRUD) of Book objects. Note that the 'list' function in Chapter 3 already implements the Read portion of CRUD (although Read normally refers to reading a single object; you could implement full Read functionality using the techniques introduced below). This section will focus on the Create and Delete aspects of CRUD. More advanced capabilities, including full Update functionality, will be addressed in Chapter 9.
-
-### FORMLESS SUBMISSION
-
-Our initial attempt at object creation will utilize the "URL arguments" feature of Cutelyst (we will employ the more common form-based submission in the sections that follow).
-
-Include a Create Action in the Books Controller
-
-Edit src/books.h and enter the following method:
-
-```C++
-public:
- ...
- /**
- * Create a book with the supplied title, rating, and author
- */
- C_ATTR(url_create, :Local :Args(3))
- void url_create(Context *c, const QString &title, const QString &rating, const QString &authorId);
-```
-
-And in src/books.cpp:
-
-```c++
-void Books::url_create(Context *c, const QString &title, const QString &rating, const QString &authorId)
-{
- // In addition to context, get the title, rating, &
- // author_id args from the URL. Note that Cutelyst automatically
- // puts extra information after the "/<controller_name>/<action_name/"
- // as QStrings. The args are separated by the '/' char on the URL.
-
- // Insert the book into it's table
- QSqlQuery query = CPreparedSqlQueryThreadForDB("INSERT INTO book (title, rating) VALUES (:title, :rating)", "MyDB");
- query.bindValue(":title", title);
- query.bindValue(":rating", rating);
- int bookId = 0;
- bool error = true;
- if (query.exec()) {
- bookId = query.lastInsertId().toInt();
-
- query = CPreparedSqlQueryThreadForDB("INSERT INTO book_author (book_id, author_id) VALUES (:book_id, :author_id)", "MyDB");
- query.bindValue(":book_id", bookId);
- query.bindValue(":author_id", authorId);
- if (query.exec()) {
- error = false;
- }
- }
-
- // On error show the last one
- if (error) {
- c->stash()["error_msg"] = query.lastError().text();
- }
-
- QVariantHash book;
- book["title"] = title;
- book["rating"] = rating;
-
- // Assign the Book object to the stash for display and set template
- c->stash({
- {"book", book},
- {"template", "books/create_done.html"}
- });
-
- // Disable caching for this page
- c->response()->setHeader("Cache-Control", "no-cache");
-}
-```
-
-Notice that Cutelyst takes "extra slash-separated information" from the URL and passes it as arguments in (as long as the number of arguments is not "fixed" using an attribute like :Args(0)). The url_create action then makes a SQL insert to add the requested information to the database (with a separate call to update the join table). As do virtually all controller methods (at least the ones that directly handle user input), it then sets the template that should handle this request.
-
-Also note that we are explicitly setting a no-cache "Cache-Control" header to force browsers using the page to get a fresh copy every time. You could even move this to a Auto method in src/Root.cpp and it would automatically get applied to every page in the whole application via a single line of code (remember from Chapter 3, that every auto method gets run in the Controller hierarchy).
-
-Include a Template for the 'url_create' Action:
-
-Edit root/src/books/create_done.html and then enter:
-
-```html
-{% comment %} Output information about the record that was added. First title.{% endcomment %}
-<p>Added book '{{ book.title }}'.</p>
-
-<p><a href="/books/list">Return to list</a></p>
-```
-
-Try the 'url_create' Feature
-
-Make sure the development server is running with the "-r" restart option and that you rebuild the application with make.
-
-Note that new path for /books/url_create appears in the startup debug output.
-
-Next, use your browser to enter the following URL:
-
- http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-Your browser should display "Added book 'TCPIP_Illustrated_Vol-2' by 'Stevens' with a rating of 5.".
-
-If you then click the "Return to list" link, you should find that there are now six books shown (if necessary, Shift+Reload or Ctrl+Reload your browser at the /books/list page).
-
-### CONVERT TO A CHAINED ACTION
-
-Although the example above uses the same Local action type for the method that we saw in the previous chapter of the tutorial, there is an alternate approach that allows us to be more specific while also paving the way for more advanced capabilities. Change the method declaration for url_create in src/books.h you entered above to match the following:
-
-```diff
-- C_ATTR(url_create, :Local :Args(3))
-+ C_ATTR(url_create, :Chained("/") :PathPart("books/url_create") :Args(3))
-```
-
-This converts the method to take advantage of the Chained action/dispatch type. Chaining lets you have a single URL automatically dispatch to several controller methods, each of which can have precise control over the number of arguments that it will receive. A chain can essentially be thought of having three parts -- a beginning, a middle, and an end. The bullets below summarize the key points behind each of these parts of a chain:
-
-* Beginning
- * Use **:Chained("/")** to start a chain
- * Get arguments through CaptureArgs() or AutoCaptureArgs
- * Specify the path to match with PathPart()
-* Middle
- * Link to previous part of the chain with :Chained("_name_")
- * Get arguments through CaptureArgs() or AutoCaptureArgs
- * Specify the path to match with PathPart()
-* End
- * Link to previous part of the chain with :Chained("_name_")
- * Do NOT get arguments through "CaptureArgs()," use "Args()" or AutoArgs instead to end a chain
- * Specify the path to match with PathPart()
-
-In our url_create method above, we have combined all three parts into a single method: :Chained("/") to start the chain, :PathPart("books/url_create") to specify the base URL to match, and :Args(3) to capture exactly three arguments and to end the chain.
-
-As we will see shortly, a chain can consist of as many "links" as you wish, with each part capturing some arguments and doing some work along the way. We will continue to use the Chained action type in this chapter of the tutorial and explore slightly more advanced capabilities with the base method and delete feature below. But Chained dispatch is capable of far more.
-
-#### Try the Chained Action
-
-If you look back at the application server startup logs from your initial version of the url_create method (the one using the :Local attribute), you will notice that it produced output similar to the following:
-
-```
-Loaded Path actions:
-.-----------------------+-------------------.
-| Path | Private |
-.-----------------------+-------------------.
-| /... | /defaultPage |
-| / | /index |
-| /books | /books/index |
-| /books/list/... | /books/list |
-| /books/url_create/... | /books/url_create |
-.-----------------------+-------------------.
-```
-
-When the application server restarts after our conversion to Chained dispatch, the debug output should change to something along the lines of the following:
-
-```
-Loaded Path actions:
-.-----------------+--------------.
-| Path | Private |
-.-----------------+--------------.
-| /... | /defaultPage |
-| / | /index |
-| /books | /books/index |
-| /books/list/... | /books/list |
-.-----------------+--------------.
-
-Loaded Chained actions:
-.-------------------------+-------------------.
-| Path Spec | Private |
-.-------------------------+-------------------.
-| /books/url_create/*/*/* | /books/url_create |
-.-------------------------+-------------------.
-```
-
-url_create has disappeared from the "Loaded Path actions" section but it now shows up under the newly created "Loaded Chained actions" section. And the "/*/*/*" portion clearly shows our requirement for three arguments.
-
-As with our non-chained version of url_create, use your browser to enter the following URL:
-
- http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-
-You should see the same "Added book 'TCPIP_Illustrated_Vol-2' by 'Stevens' with a rating of 5.". Click the "Return to list" link, and you should find that there are now seven books shown (two copies of TCPIP_Illustrated_Vol-2).
-
-#### Refactor to Use a 'base' Method to Start the Chains
-
-Let's make a quick update to our initial Chained action to show a little more of the power of chaining. First, open src/books.h in your editor and add the following method:
-
-```c++
-class Books
-{
-public:
- /**
- * Can place common logic to start chained dispatch here
- */
- C_ATTR(base, :Chained("/") :PathPart("books") :CaptureArgs(0))
- void base(Context *c);
-}
-```
-
-And on src/books.cpp:
-
-```c++
-void Books::base(Context *c)
-{
- // Print a message to the debug log
- qDebug("*** INSIDE BASE METHOD ***");
-}
-```
-
-Here we print a log message. If your controller always needs a book ID as its first argument, you could have the base method capture that argument (with :CaptureArgs(1)) and use it to pull the book object and leave it in the stash for later parts of your chains to then act upon. Because we have several actions that don't need to retrieve a book (such as the url_create we are working with now), we will instead add that functionality to a common object action shortly.
-
-As for url_create, let's modify it to first dispatch to base. Open up src/books.h and edit the declaration for url_create to match the following:
-
-```c++
-- C_ATTR(url_create, :Chained("/") :PathPart("books/url_create") :Args(3))
-+ C_ATTR(url_create, :Chained("base") :PathPart("url_create") :Args(3))
-```
-
-Once you rebuild your application, notice that the development server will restart and our "Loaded Chained actions" section will changed slightly:
-
-```
-Loaded Chained actions:
-.-------------------------+-----------------------.
-| Path Spec | Private |
-.-------------------------+-----------------------.
-| /books/url_create/*/*/* | /books/base (0) |
-| | => /books/url_create |
-.-------------------------+-----------------------.
-```
-
-The "Path Spec" is the same, but now it maps to two Private actions as we would expect. The base method is being triggered by the /books part of the URL. However, the processing then continues to the url_create method because this method "chained" off base and specified :PathPart("url_create") (note that we could have omitted the "PathPart" here because it matches the name of the method, but we will include it to make the logic as explicit as possible).
-
-Once again, enter the following URL into your browser:
-
- http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-
-The same "Added book 'TCPIP_Illustrated_Vol-2' by 'Stevens' with a rating of 5." message and a dump of the new book object should appear. Also notice the extra "INSIDE BASE METHOD" debug message in the development server output from the base method. Click the "Return to list" link, and you should find that there are now eight books shown. (You may have a larger number of books if you repeated any of the "create" actions more than once. Don't worry about it as long as the number of books is appropriate for the number of times you added new books... there should be the original five books added via myapp01.sql plus one additional book for each time you ran one of the url_create variations above.)
-
-### MANUALLY BUILDING A CREATE FORM
-
-Although the url_create action in the previous step does begin to reveal the power and flexibility of Cutelyst, it's obviously not a very realistic example of how users should be expected to enter data. This section begins to address that concern (but just barely, see Chapter 9 for better options for handling web-based forms).
-
-#### Add Method to Display The Form
-
-Edit src/books.h and add the following method:
-
-```c++
-class Books : public Controller
-{
-public:
- ...
- /**
- * Display form to collect information for book to create
- */
- C_ATTR(form_create, :Chained("base") :PathPart("form_create") :Args(0))
- void form_create(Context *c);
-};
-```
-
-```C++
-void Books::form_create(Context *c)
-{
- // Set the Grantlee Template to use
- c->setStash("template", "books/form_create.html");
-}
-```
-
-This action simply invokes a view containing a form to create a book.
-
-#### Add a Template for the Form
-
-Open root/src/books/form_create.html in your editor and enter:
-
-```html
-<form method="post" action="form_create_do">
-<table>
- <tr><td>Title:</td><td><input type="text" name="title"></td></tr>
- <tr><td>Rating:</td><td><input type="text" name="rating"></td></tr>
- <tr><td>Author ID:</td><td><input type="text" name="author_id"></td></tr>
-</table>
-<input type="submit" name="Submit" value="Submit">
-</form>
-```
-
-Note that we have specified the target of the form data as form_create_do, the method created in the section that follows.
-
-#### Add a Method to Process Form Values and Update Database
-
-Edit src/books.h and add the following method to save the form information to the database:
-
-```c++
-class Books : public Controller
-{
-public:
- ...
- /**
- * Take information from form and add to database
- */
- C_ATTR(form_create_do, :Chained("base") :PathPart("form_create_do") :Args(0))
- void form_create_do(Context *c);
-};
-```
-
-```c++
-void Books::form_create_do(Context *c)
-{
- // Retrieve the values from the form
- QString title = c->request()->bodyParam("title", "N/A");
- QString rating = c->request()->bodyParam("rating", "N/A");
- QString authorId = c->request()->bodyParam("author_id", "1");
-
- // Insert the book into it's table
- QSqlQuery query = CPreparedSqlQueryThreadForDB("INSERT INTO book (title, rating) VALUES (:title, :rating)", "MyDB");
- query.bindValue(":title", title);
- query.bindValue(":rating", rating);
- int bookId = 0;
- bool error = true;
- if (query.exec()) {
- bookId = query.lastInsertId().toInt();
-
- query = CPreparedSqlQueryThreadForDB("INSERT INTO book_author (book_id, author_id) VALUES (:book_id, :author_id)", "MyDB");
- query.bindValue(":book_id", bookId);
- query.bindValue(":author_id", authorId);
- if (query.exec()) {
- error = false;
- }
- }
-
- // On error show the last one
- if (error) {
- c->stash()["error_msg"] = query.lastError().text();
- }
-
- // Assign the Book object to the stash for display and set template
- c->stash({
- {"book", QVariant::fromValue(c->request()->bodyParams())},
- {"template", "books/create_done.html"}
- });
-}
-```
-
-#### Test Out The Form
-
-Notice that the server startup log reflects the two new chained methods that we added:
-
-```
-Loaded Chained actions:
-.-------------------------+--------------------------.
-| Path Spec | Private |
-.-------------------------+--------------------------.
-| /books/form_create | /books/base (0) |
-| | => /books/form_create |
-| /books/form_create_do | /books/base (0) |
-| | => /books/form_create_do |
-| /books/url_create/*/*/* | /books/base (0) |
-| | => /books/url_create |
-.-------------------------+--------------------------.
-```
-
-Point your browser to http://localhost:3000/books/form_create and enter "TCP/IP Illustrated, Vol 3" for the title, a rating of 5, and an author ID of 4. You should then see the output of the same create_done.html template seen in earlier examples. Finally, click "Return to list" to view the full list of books.
-
-Note: Having the user enter the primary key ID for the author is obviously crude; we will address this concern with a drop-down list and add validation to our forms in Chapter 9.
-
-### A SIMPLE DELETE FEATURE
-
-Turning our attention to the Delete portion of CRUD, this section illustrates some basic techniques that can be used to remove information from the database.
-
-#### Include a Delete Link in the List
-
-Edit root/src/books/list.html and update it to match the following (two sections have changed):
-1) the additional `<th>Links</th>` table header, and 2) the four lines `<td>...</td>` for the Delete link near the bottom):
-
-```html
-{% comment %}This is a Grantlee comment.{% endcomment %}
-
-{% comment %}Some basic HTML with a loop to display books{% endcomment %}
-<table>
-<tr><th>Title</th><th>Rating</th><th>Author(s)</th><th>Links</th></tr>
-{% comment %}Display each book in a table row{% endcomment %}
-{% for book in books %}
- <tr>
- <td>{{ book.title }}</td>
- <td>{{ book.rating }}</td>
- <td></td>
- <td>
- {% comment %} Add a link to delete a book {% endcomment %}
- <a href="/books/id/{{ book.id }}/delete">Delete</a>
- </td>
- </tr>
-{% endfor %}
-</table>
-```
-
-The additional code is obviously designed to add a new column to the right side of the table with a Delete "button" (for simplicity, links will be used instead of full HTML buttons; but, in practice, anything that modifies data should be handled with a form sending a POST request).
-
-**Note:** In practice you should never use a GET request to delete a record -- always use POST for actions that will modify data. We are doing it here for illustrative and simplicity purposes only.
-
-#### Add a Common Method to Retrieve a Book for the Chain
-
-As mentioned earlier, since we have a mixture of actions that operate on a single book ID and others that do not, we should not have `base` capture the book ID, find the corresponding book in the database and save it in the stash for later links in the chain. However, just because that logic does not belong in `base` doesn't mean that we can't create another location to centralize the book lookup code. In our case, we will create a method called object that will store the specific book in the stash. Chains that always operate on a single existing book can chain off this method, but methods such as url_create that don't operate on an existing book can chain directly off base.
-
-To add the object method, edit src/books.h and add the following code:
-
-```c++
-class Books : public Controller
-{
-public:
- ...
- /**
- * Fetch the specified book object based on the book ID and store
- * it in the stash
- */
- C_ATTR(object, :Chained("base") :PathPart("id") :CaptureArgs(1))
- void object(Context *c, const QString &id);
-};
-```
-
-```c++
-void Books::object(Context *c, const QString &id)
-{
- // Find the object on the database
- QSqlQuery query = CPreparedSqlQueryThreadForDB("SELECT * FROM book WHERE id = :id", "MyDB");
- query.bindValue(":id", id);
- if (query.exec()) {
- c->setStash("object", Sql::queryToHashObject(query));
- } else {
- // You would probably want to do something like this in a real app:
- // c->detach("/error_404");
- }
- qDebug() << "*** INSIDE OBJECT METHOD for obj id=" << id << " ***";
-}
-```
-
-Now, any other method that chains off object will automatically have the appropriate book waiting for it in c->stash("object").
-
-#### Add a Delete Action to the Controller
-
-Open src/books.h in your editor and add the following method:
-
-```c++
-class Books : public Controller
-{
-public:
- ...
- /**
- * Fetch the specified book object based on the book ID and store
- * it in the stash
- */
- C_ATTR(delete_obj, :Chained("object") :PathPart("delete") :Args(0))
- void delete_obj(Context *c);
-};
-```
-
-```c++
-void Books::delete_obj(Context *c)
-{
- QVariantHash book = c->stash("object").toHash();
-
- // Delete the object on the database
- QSqlQuery query = CPreparedSqlQueryThreadForDB("DELETE FROM book WHERE id = :id", "MyDB");
- query.bindValue(":id", book.value("id"));
- if (query.exec()) {
- // Set a status message to be displayed at the top of the view
- c->setStash("status_msg", "Book deleted.");
- }
-
- // Forward to the list action/method in this controller
- c->forward("list");
-}
-```
-
-This method first deletes the book object saved by the object method.
-
-Then, rather than forwarding to a "delete done" page as we did with the earlier create example, it simply sets the status_msg to display a notification to the user as the normal list view is rendered.
-
-The delete action uses the context forward method to return the user to the book list. The detach method could have also been used. Whereas forward returns to the original action once it is completed, detach does not return. Other than that, the two are equivalent.
-
-#### Try the Delete Feature
-
-Once you save the Books controller, the server should automatically restart. The delete method should now appear in the "Loaded Chained actions" section of the startup debug output:
-
-```
-Loaded Chained actions:
-.-------------------------+--------------------------.
-| Path Spec | Private |
-.-------------------------+--------------------------.
-| /books/id/*/delete | /books/base (0) |
-| | -> /books/object (1) |
-| | => /books/delete_obj |
-| /books/form_create | /books/base (0) |
-| | => /books/form_create |
-| /books/form_create_do | /books/base (0) |
-| | => /books/form_create_do |
-| /books/url_create/*/*/* | /books/base (0) |
-| | => /books/url_create |
-.-------------------------+--------------------------.
-```
-
-Then point your browser to http://localhost:3000/books/list and click the "Delete" link next to the first "TCPIP_Illustrated_Vol-2". A green "Book deleted" status message should display at the top of the page, along with a list of the eight remaining books.
-
-#### Fixing a Dangerous URL
-
-Note the URL in your browser once you have performed the deletion in the prior step -- it is still referencing the delete action:
-
- http://localhost:3000/books/id/6/delete
-
-What if the user were to press reload with this URL still active? In this case the redundant delete is harmless (although it does generate an exception screen, it doesn't perform any undesirable actions on the application or database), but in other cases this could clearly lead to trouble.
-
-We can improve the logic by converting to a redirect. Unlike c->forward("list") or c->detach("list") that perform a server-side alteration in the flow of processing, a redirect is a client-side mechanism that causes the browser to issue an entirely new request. As a result, the URL in the browser is updated to match the destination of the redirection URL.
-
-To convert the forward used in the previous section to a redirect, open `src/books.cpp` and edit the existing delete_obj method to match:
-
-```c++
-void Books::delete_obj(Context *c)
-{
- QVariantHash book = c->stash("object").toHash();
-
- // Delete the object on the database
- QSqlQuery query = CPreparedSqlQueryThreadForDB("DELETE FROM book WHERE id = :id", "MyDB");
- query.bindValue(":id", book.value("id"));
- if (query.exec()) {
- // Set a status message to be displayed at the top of the view
- c->setStash("status_msg", "Book deleted.");
- }
-
- // Redirect the user back to the list page. Note the use
- // of actionFor as earlier in this section (BasicCRUD)
- c->response()->redirect(c->uriFor(CActionFor("list")));
-}
-```
-
-#### Try the Delete and Redirect Logic
-
-Point your browser to http://localhost:3000/books/list (don't just hit "Refresh" in your browser since we left the URL in an invalid state in the previous section!) and delete the first copy of the remaining two "TCPIP_Illustrated_Vol-2" books. The URL in your browser should return to the http://localhost:3000/books/list URL, so that is an improvement, but notice that no green "Book deleted" status message is displayed. Because the stash is reset on every request (and a redirect involves a second request), the status_msg is cleared before it can be displayed.
-
-#### Using 'uriFor' to Pass Query Parameters
-
-There are several ways to pass information across a redirect. One option is to use the flash technique that we will see in Chapter 5 of this tutorial; however, here we will pass the information via query parameters on the redirect itself. Open src/books.cpp and update the existing sub delete method to match the following:
-
-```c++
-void Books::delete_obj(Context *c)
-{
- QVariantHash book = c->stash("object").toHash();
- QString statusMsg;
-
- // Delete the object on the database
- QSqlQuery query = CPreparedSqlQueryThreadForDB("DELETE FROM book WHERE id = :id", "MyDB");
- query.bindValue(":id", book.value("id"));
- if (query.exec()) {
- // Set a status message to be displayed at the top of the view
- statusMsg = "Book deleted.";
- } else {
- // Set an error message to be displayed at the top of the view
- statusMsg = query.lastError().text();
- }
-
- // Redirect the user back to the list page. Note the use
- // of actionFor as earlier in this section (BasicCRUD)
- c->response()->redirect(c->uriFor(CActionFor("list"), ParamsMultiMap{
- {"status_msg", statusMsg }
- }));
-}
-```
-
-This modification simply leverages the ability of `uriFor` to include an arbitrary number of name/value pairs in a ParamsMultiMap. Next, we need to update `root/src/wrapper.html` to handle status_msg as a query parameter:
-
-```
-...
-<div id="content">
- {% comment %} Status and error messages {% endcomment %}
- <span class="message">{{ status_msg }}{{ c.request.queryParams.status_msg|escape }}</span>
- <span class="error">{{ error_msg }}</span>
- {% comment %} This is where Grantlee will stick all of your template's contents.{% endcomment %}
- {{ content }}
-</div><!-- end content -->
-...
-```
-
-Although the sample above only shows the content div, leave the rest of the file intact -- the only change we made to the `wrapper.html` was to add `{{ c.request.queryParams.status_msg|escape }}` to the `<span class="message">` line. Note that we definitely want the `|escape` Grantlee filter here since it would be easy for users to modify the message on the URL and possibly inject harmful code into the application if we left that off, by default Grantlee escapes all strings but if in future you decide to change the default behavior this is in place.
-
-#### Try the Delete and Redirect With Query Param Logic
-
-Point your browser to `http://localhost:3000/books/list` (you should now be able to safely hit "refresh" in your browser). Then delete the remaining copy of "TCPIP_Illustrated_Vol-2". The green "Book deleted" status message should return. But notice that you can now hit the "Reload" button in your browser and it just redisplays the book list (and it correctly shows it without the "Book deleted" message on redisplay).
-
-NOTE: Be sure to check out Authentication where we use an improved technique that is better suited to your real world applications.
-
-You can jump to the next chapter of the tutorial here: [Authentication](Tutorial_05_Authentication)
diff --git a/Tutorial_05_Authentication.md b/Tutorial_05_Authentication.md
deleted file mode 100644
index 62042f29971b..000000000000
--- a/Tutorial_05_Authentication.md
+++ /dev/null
@@ -1,366 +0,0 @@
-### OVERVIEW
-
-1. [Introduction](Tutorial_01_Intro)
-2. [Cutelyst Basics](Tutorial_02_CutelystBasics)
-3. [More Cutelyst Basics](Tutorial_03_MoreCutelystBasics)
-4. [Basic CRUD](Tutorial_04_BasicCRUD)
-5. [Authentication](Tutorial_05_Authentication)
-
-### DESCRIPTION
-
-Now that we finally have a simple yet functional application, we can focus on providing authentication (with authorization coming next in Chapter 6).
-
-### BASIC AUTHENTICATION
-
-This section explores how to add authentication logic to a Cutelyst application.
-
-#### Add Users and Roles to the Database
-
-First, we add both user and role information to the database (we will add the role information here although it will not be used until the authorization section, Chapter 6). Create a new SQL script file by opening myapp02.sql in your editor and insert:
-
-```sql
---
--- Add users and role tables, along with a many-to-many join table
---
-PRAGMA foreign_keys = ON;
-CREATE TABLE users (
- id INTEGER PRIMARY KEY,
- username TEXT,
- password TEXT,
- email_address TEXT,
- first_name TEXT,
- last_name TEXT,
- active INTEGER
-);
-CREATE TABLE role (
- id INTEGER PRIMARY KEY,
- role TEXT
-);
-CREATE TABLE user_role (
- user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
- role_id INTEGER REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
- PRIMARY KEY (user_id, role_id)
-);
---
--- Load up some initial test data
---
-INSERT INTO users VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1);
-INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1);
-INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', 'No', 'Go', 0);
-INSERT INTO role VALUES (1, 'user');
-INSERT INTO role VALUES (2, 'admin');
-INSERT INTO user_role VALUES (1, 1);
-INSERT INTO user_role VALUES (1, 2);
-INSERT INTO user_role VALUES (2, 1);
-INSERT INTO user_role VALUES (3, 1);
-```
-
-Then load this into the myapp.db database with the following command:
-
-```bash
-$ sqlite3 myapp.db < myapp02.sql
-```
-
-#### Include Authentication and Session Plugins
-
-Edit src/CMakeLists.txt and update it as follows:
-
-```cmake
-target_link_libraries(MyApp
- ...
- Cutelyst::Session # Add these lines
- Cutelyst::Authentication # Add these lines
- ...
-}
-```
-
-#### Create a Authentication Store
-
-Cutelyst comes with a base AuthenticationStore class that can be subclassed to allow for integration with Authentication class, since we are not using an ORM this class will
-be used to fetch an user from the database we just changed.
-
-Create a src/authstoresql.h with the following content:
-
-```c++
-#ifndef AUTHSTORESQL_H
-#define AUTHSTORESQL_H
-
-#include <Cutelyst/Plugins/Authentication/authenticationstore.h>
-
-using namespace Cutelyst;
-
-class AuthStoreSql : public AuthenticationStore
-{
-public:
- explicit AuthStoreSql(QObject *parent = 0);
-
- virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo) override;
-
-private:
- QString m_idField;
-};
-
-#endif // AUTHSTORESQL_H
-```
-
-And src/authstore.cpp:
-
-```c++
-#include "authstoresql.h"
-
-#include <Cutelyst/Plugins/Utils/Sql>
-
-#include <QSqlQuery>
-#include <QSqlRecord>
-#include <QSqlError>
-#include <QDebug>
-
-AuthStoreSql::AuthStoreSql(QObject *parent) : AuthenticationStore(parent)
-{
- m_idField = "username";
-}
-
-AuthenticationUser AuthStoreSql::findUser(Context *c, const ParamsMultiMap &userinfo)
-{
- QString id = userinfo[m_idField];
-
- QSqlQuery query = CPreparedSqlQueryThreadForDB("SELECT * FROM users WHERE username = :username", "MyDB");
- query.bindValue(":username", id);
- if (query.exec() && query.next()) {
- QVariant userId = query.value("id");
- qDebug() << "FOUND USER -> " << userId.toInt();
- AuthenticationUser user(userId.toString());
-
- int columns = query.record().count();
- // send column headers
- QStringList cols;
- for (int j = 0; j < columns; ++j) {
- cols << query.record().fieldName(j);
- }
-
- for (int j = 0; j < columns; ++j) {
- user.insert(cols.at(j),
- query.value(j).toString());
- }
-
- return user;
- }
- qDebug() << query.lastError().text();
-
- return AuthenticationUser();
-}
-```
-
-#### Configure Authentication
-
-Edit src/myapp.cpp and update it as follows:
-
-```c++
-#include <Cutelyst/Plugins/Session/Session>
-#include <Cutelyst/Plugins/Authentication/authentication.h>
-#include <Cutelyst/Plugins/Authentication/credentialpassword.h>
-
-#include "authstoresql.h"
-
-bool MyApp::init()
-{
- ...
- new Session(this);
-
- auto auth = new Authentication(this);
- auto credential = new CredentialPassword;
- credential->setPasswordType(CredentialPassword::Clear);
-
- auth->addRealm(new AuthStoreSql, credential);
- ...
-}
-```
-
-The `Authentication` plugin supports Authentication while the Session plugins are required to maintain state across multiple HTTP requests.
-
-There are different password types. Here we make use of the `CredentialPassword::Clear` since our SQL data is in clear text. On production you want Hashed and in order to obtain a password hash e.g. to store in a database you can use something like
-
-```c++
-QByteArray hashedPassword = CredentialPassword::createPassword(
- password.toUtf8(),
- QCryptographicHash::Sha256,
- 3, // iterations
- 16, // bytes salt
- 16 // bytes hash
-);
-```
-
-#### Add Login and Logout Controllers
-
-Use the Cutelyst command to create two stub controller files:
-
-```bash
-$ cutelyst --controller Login
-$ cutelyst --controller Logout
-```
-
-You could easily use a single controller here. For example, you could have a User controller with both login and logout actions. Remember, Cutelyst is designed to be very flexible, and leaves such matters up to you, the designer and programmer.
-
-Then open src/login.cpp, and update the definition of the method index to match:
-
-```c++
-#include <Cutelyst/Plugins/Authentication/authentication.h>
-
-void Login::index(Context *c)
-{
- // Get the username and password from form
- QString username = c->request()->bodyParam("username");
- QString password = c->request()->bodyParam("password");
-
- // If the username and password values were found in form
- if (!username.isNull() && !password.isNull()) {
- // Attempt to log the user in
- if (Authentication::authenticate(c, { {"username", username}, {"password", password} })) {
- // If successful, then let them use the application
- c->response()->redirect(c->uriFor(c->controller("Books")->actionFor("list")));
- return;
- } else {
- // Set an error message
- c->setStash("error_msg", "Bad username or password.");
- }
- } else if (!Authentication::userExists(c)) {
- // Set an error message
- c->setStash("error_msg", "Empty username or password.");
- }
-
- // If either of above don't work out, send to the login page
- c->setStash("template", "login.html");
-}
-```
-
-This controller fetches the username and password values from the login form and attempts to authenticate the user. If successful, it redirects the user to the book list page. If the login fails, the user will stay at the login page and receive an error message. If the username and password values are not present in the form, the user will be taken to the empty login form.
-
-Note that we could have used something like `C_ATTR(index, :Path)`, however, it is generally recommended (partly for historical reasons, and partly for code clarity) only to use an action that matches everything in Root controller of MyApp, and then mainly to generate the 404 not found page for the application.
-
-Instead, we are using `C_ATTR(somename, :Path :Args(0))` here to specifically match the URL /login. Path actions (aka, "literal actions") create URI matches relative to the namespace of the controller where they are defined. Although Path supports arguments that allow relative and absolute paths to be defined, here we use an empty Path definition to match on just the name of the controller itself. The method name, index, is arbitrary. We make the match even more specific with the :Args(0) action modifier -- this forces the match on only /login, not /login/somethingelse.
-
-Next, update the corresponding method in src/logout.cpp to match:
-
-```c++
-#include <Cutelyst/Plugins/Authentication/authentication.h>
-
-void Logout::index(Context *c)
-{
- // Clear the user's state
- Authentication::logout(c);
-
- // Send the user to the starting point
- c->response()->redirect(c->uriFor("/"));
-}
-```
-
-#### Include Login and Logout Controllers
-
-Edit src/myapp.cpp and update it as follows:
-
-```c++
-#include "login.h"
-#include "logout.h"
-
-bool MyApp::init()
-{
- ...
- new Login(this);
-
- new Logout(this);
- ...
-}
-```
-
-#### Add a Login Form Template Page
-
-Create a login form by opening root/src/login.html and inserting:
-
-```html
-<form method="post" action="/login">
- <table>
- <tr>
- <td>Username:</td>
- <td><input type="text" name="username" size="40" /></td>
- </tr>
- <tr>
- <td>Password:</td>
- <td><input type="password" name="password" size="40" /></td>
- </tr>
- <tr>
- <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
- </tr>
- </table>
-</form>
-```
-
-#### Add Valid User Check
-
-We need something that provides enforcement for the authentication mechanism -- a global mechanism that prevents users who have not passed authentication from reaching any pages except the login page. This is generally done via an Auto action/method in src/root.cpp.
-
-Edit the existing src/root.h and src/root.cpp class file and insert the following method:
-
-```c++
-class Root : public Controller
-{
- ...
-private:
- /**
- * Check if there is a user and, if not, forward to login page
- */
- C_ATTR(Auto, :Private)
- bool Auto(Context *c);
-};
-```
-
-```c++
-#include <Cutelyst/Plugins/Authentication/authentication.h>
-
-bool Root::Auto(Context *c)
-{
- // Allow unauthenticated users to reach the login page. This
- // allows unauthenticated users to reach any action in the Login
- // controller. To lock it down to a single action, we could use:
- // if (c->action() eq c->controller("Login")->actionFor("index"))
- // to only allow unauthenticated access to the 'index' action we
- // added above
- if (c->controller() == c->controller("Login")) {
- return true;
- }
-
- // If a user doesn't exist, force login
- if (!Authentication::userExists(c)) {
- // Dump a log message to the development server debug output
- qDebug("***Root::Auto User not found, forwarding to /login");
-
- // Redirect the user to the login page
- c->response()->redirect(c->uriFor("/login"));
-
- // Return false to cancel 'post-auto' processing and prevent use of application
- return false;
- }
-
- // User found, so return true to continue with processing after this 'auto'
- return true;
-}
-```
-
-As discussed in "CREATE A CUTELYST CONTROLLER" in Cutelyst::Tutorial::03_MoreCutelystBasics, every Auto method from the application/root controller down to the most specific controller will be called. By placing the authentication enforcement code inside the auto method of src/root.cpp, it will be called for every request that is received by the entire application.
-
-#### Try Out Authentication
-
-The development server should have reloaded each time we recompiled the application in the previous section. Now try going to http://localhost:3000/books/list and you should be redirected to the login page, hitting Shift+Reload or Ctrl+Reload if necessary. Note the `***Root::Auto User not found...` debug message in the application server output. Enter username `test01` and password `mypass`, and you should be taken to the Book List page.
-
-IMPORTANT NOTE: If you are having issues with authentication on Internet Explorer (or potentially other browsers), be sure to check the system clocks on both your server and client machines. Internet Explorer is very picky about timestamps for cookies. You can use the `ntpq -p` command to check time sync and/or use the following command to force a sync:
-
-```bash
-$ sudo ntpdate-debian
-```
-
-Or, depending on your firewall configuration, try it with `-u`:
-
-```bash
- sudo ntpdate-debian -u
-```
-
-Note: NTP can be a little more finicky about firewalls because it uses UDP vs. the more common TCP that you see with most Internet protocols. Worse case, you might have to manually set the time on your development box instead of using NTP.
diff --git a/proc.sh b/proc.sh
index d88f00d1be36..dee698c355d3 100755
--- a/proc.sh
+++ b/proc.sh
@@ -6,13 +6,12 @@ if [[ $outDir == '' ]]; then
exit 1
fi
-for mdInFile in Tutorial*.md; do
-
- mkdir -p ${outDir}
+mkdir -p ${outDir}
+for mdInFile in *.md; do
htmlOutFile=$(basename ${mdInFile} .md)".html"
echo "Processing ${mdInFile} -> ${htmlOutFile}"
-cat <<EOF > ${outDir}/${htmlOutFile}
+ cat <<EOF > ${outDir}/${htmlOutFile}
<!DOCTYPE html>
<html>
<head>
@@ -23,13 +22,13 @@ cat <<EOF > ${outDir}/${htmlOutFile}
<body>
EOF
-markdown -f +fencedcode ${mdInFile} >> ${outDir}/${htmlOutFile}
+ markdown -f +fencedcode ${mdInFile} >> ${outDir}/${htmlOutFile}
-cat <<EOF >> ${outDir}/${htmlOutFile}
+ cat <<EOF >> ${outDir}/${htmlOutFile}
</body>
</html>
EOF
-sed -i -E 's/href="Tutorial(.+?)"/href="Tutorial\1.html"/g' ${outDir}/${htmlOutFile}
+sed -i -E 's/a href="(.+?)"/a href="\1.html"/g' ${outDir}/${htmlOutFile}
done