Add creation of posts with logging

This commit is contained in:
finga 2021-01-12 17:55:25 +01:00
parent 56544cd0d4
commit 4948195495
13 changed files with 379 additions and 97 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/rust_web.pdf
/rust_web.tex
/simple_text_board/target
/simple_text_board/simple_text_board.sqlite

View file

@ -268,13 +268,23 @@ cargo r
#+END_SRC
**** Setup Rocket
Add Rocket dependency to ~Cargo.toml~:
***** Add Rocket to ~Cargo.toml~
#+BEGIN_SRC toml
[dependencies]
rocket = "0.4"
#+END_SRC
**** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program
***** Add ~Rocket.toml~
#+BEGIN_SRC toml
[development]
address = "0.0.0.0"
port = 8000
workers = 2
keep_alive = 5
log = "normal"
#+END_SRC
***** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program
#+BEGIN_SRC rust
#![feature(proc_macro_hygiene, decl_macro)]
@ -290,17 +300,8 @@ fn main() {
}
#+END_SRC
**** Add ~Rocket.toml~
#+BEGIN_SRC toml
[development]
address = "0.0.0.0"
port = 8000
workers = 2
keep_alive = 5
log = "normal"
#+END_SRC
**** Create the base template at ~templates/base.html.tera~
**** Create first templates
***** Create the base template at ~templates/base.html.tera~
#+BEGIN_SRC html
<!DOCTYPE html>
<html>
@ -319,16 +320,13 @@ log = "normal"
</html>
#+END_SRC
**** Create the create template at ~templates/create.html.tera~
***** Create the create template at ~templates/create.html.tera~
#+BEGIN_SRC html
{% extends "base" %}
{% block content %}
<h1>simple text board</h1>
<h2>create a post</h2>
{% if flash %}
<p>{{ flash }}</p>
{% endif %}
<form action="/create" method="post" accept-charset="utf-8">
<label for="author">author name</label>
<input type="text" placeholder="author" name="author" required>
@ -344,12 +342,15 @@ log = "normal"
</textarea>
<button type="submit">create</button>
{% if flash %}<p>{{ flash }}</p>{% endif %}
</form>
{% endblock content %}
#+END_SRC
**** Create the ~create_form~ function
***** Create the ~create_form~ function and add it to routing
#+BEGIN_SRC rust
#![feature(proc_macro_hygiene, decl_macro)]
use std::collections::HashMap;
use rocket::{get, request::FlashMessage, routes};
@ -365,6 +366,173 @@ fn create_form(flash: Option<FlashMessage>) -> Template {
Template::render("create", &context)
}
...
fn main() {
rocket::ignite()
.mount("/", routes![index, create_form])
.attach(Template::fairing())
.launch();
}
#+END_SRC
**** Setup Database and posts
***** Create the ~.env~ file
#+BEGIN_SRC sh
DATABASE_URL=simple_text_board.sqlite
#+END_SRC
***** Add diesel to the dependencies and to ~rocket_contrib~ in ~Cargo.toml~
#+BEGIN_SRC toml
...
chrono = "0.4"
diesel = { version = "1.4", features = ["sqlite", "chrono"] }
log = "0.4"
...
[dependencies.rocket_contrib]
...
features = ["diesel_sqlite_pool", "tera_templates"]
#+END_SRC
***** Configure database for Rocket in ~Rocket.toml~
#+BEGIN_SRC toml
[global.databases]
sqlite = { url = "simple_text_board.sqlite" }
#+END_SRC
***** Create the first table schema
#+BEGIN_SRC sh
diesel setup
diesel migration generate create_posts
#+END_SRC
***** How to create the table
#+BEGIN_SRC sql
CREATE TABLE posts (
id INTEGER NOT NULL PRIMARY KEY,
parent INTEGER,
timestamp DATETIME NOT NULL,
author VARCHAR NOT NULL,
email VARCHAR NOT NULL,
title VARCHAR NOT NULL,
content VARCHAR NOT NULL,
FOREIGN KEY (parent) REFERENCES posts(id)
);
#+END_SRC
***** How to destroy it
#+BEGIN_SRC sql
DROP TABLE posts;
#+END_SRC
***** Applay and test rollback
#+BEGIN_SRC sh
diesel migration run
diesel migration redo
#+END_SRC
***** Create data model in ~src/models.rs~
#+BEGIN_SRC rust
use crate::schema::posts::{self, dsl::posts as table_posts};
use chrono::NaiveDateTime;
use diesel::{QueryResult, RunQueryDsl};
#[derive(Insertable)]
#[table_name = "posts"]
pub struct NewPost<'a> {
pub parent: Option<&'a i32>,
pub timestamp: NaiveDateTime,
pub author: &'a str,
pub email: &'a str,
pub title: &'a str,
pub content: &'a str,
}
impl NewPost<'_> {
pub fn insert(&self, conn: &diesel::SqliteConnection) -> QueryResult<usize> {
diesel::insert_into(table_posts).values(self).execute(conn)
}
}
#+END_SRC
***** Add create post functionality to ~src/main.rs~
#+BEGIN_SRC rust
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate diesel;
use std::{collections::HashMap, net::SocketAddr};
use chrono::Local;
use diesel::SqliteConnection;
use log::{error, info};
use rocket::{
get, post,
request::{FlashMessage, Form},
response::{Flash, Redirect},
routes, uri, FromForm,
};
use rocket_contrib::{database, templates::Template};
mod models;
mod schema;
use models::NewPost;
#[database("sqlite")]
struct DbCon(SqliteConnection);
#[derive(FromForm)]
pub struct PostForm {
author: String,
email: String,
title: String,
content: String,
}
...
#[post("/create", data = "<post>")]
fn create_post(
conn: DbCon,
remote_address: SocketAddr,
post: Form<PostForm>,
) -> Result<Redirect, Flash<Redirect>> {
let post = NewPost {
parent: None,
timestamp: Local::now().naive_local(),
author: &post.author.trim(),
email: &post.email.trim(),
title: &post.title.trim(),
content: &post.content.trim(),
};
match post.insert(&conn) {
Ok(_) => {
info!("New post from {}", remote_address);
Ok(Redirect::to(uri!(index)))
}
Err(e) => {
error!("Could not create post from {}: {:?}", remote_address, e);
Err(Flash::error(
Redirect::to(uri!(create_form)),
"Could not create post.".to_string(),
))
}
}
}
...
fn main() {
rocket::ignite()
.mount("/", routes![index, create_form, create_post])
.attach(DbCon::fairing())
.attach(Template::fairing())
.launch();
}
#+END_SRC
* Conclusion

1
simple_text_board/.env Normal file
View file

@ -0,0 +1 @@
DATABASE_URL=simple_text_board.sqlite

View file

@ -270,6 +270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c"
dependencies = [
"byteorder",
"chrono",
"diesel_derives",
"libsqlite3-sys",
"r2d2",
@ -1163,6 +1164,9 @@ dependencies = [
name = "simple_text_board"
version = "0.1.0"
dependencies = [
"chrono",
"diesel",
"log 0.4.13",
"rocket",
"rocket_contrib",
]

View file

@ -6,8 +6,11 @@ edition = "2018"
[dependencies]
rocket = "0.4"
chrono = "0.4"
diesel = { version = "1.4", features = ["sqlite", "chrono"] }
log = "0.4"
[dependencies.rocket_contrib]
version = "0.4"
default-features = false
features = ["tera_templates"]
features = ["diesel_sqlite_pool", "tera_templates"]

View file

@ -1,3 +1,6 @@
[global.databases]
sqlite = { url = "simple_text_board.sqlite" }
[development]
address = "0.0.0.0"
port = 8000

View file

@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

View file

@ -0,0 +1 @@
DROP TABLE posts;

View file

@ -0,0 +1,10 @@
CREATE TABLE posts (
id INTEGER NOT NULL PRIMARY KEY,
parent INTEGER,
timestamp DATETIME NOT NULL,
author VARCHAR NOT NULL,
email VARCHAR NOT NULL,
title VARCHAR NOT NULL,
content VARCHAR NOT NULL,
FOREIGN KEY (parent) REFERENCES posts(id)
);

View file

@ -1,9 +1,36 @@
#![feature(proc_macro_hygiene, decl_macro)]
use std::collections::HashMap;
#[macro_use]
extern crate diesel;
use rocket::{get, request::FlashMessage, routes};
use rocket_contrib::templates::Template;
use std::{collections::HashMap, net::SocketAddr};
use chrono::Local;
use diesel::SqliteConnection;
use log::{error, info};
use rocket::{
get, post,
request::{FlashMessage, Form},
response::{Flash, Redirect},
routes, uri, FromForm,
};
use rocket_contrib::{database, templates::Template};
mod models;
mod schema;
use models::NewPost;
#[database("sqlite")]
struct DbCon(SqliteConnection);
#[derive(FromForm)]
struct PostForm {
author: String,
email: String,
title: String,
content: String,
}
#[get("/create")]
fn create_form(flash: Option<FlashMessage>) -> Template {
@ -16,6 +43,36 @@ fn create_form(flash: Option<FlashMessage>) -> Template {
Template::render("create", &context)
}
#[post("/create", data = "<post>")]
fn create_post(
conn: DbCon,
remote_address: SocketAddr,
post: Form<PostForm>,
) -> Result<Redirect, Flash<Redirect>> {
let post = NewPost {
parent: None,
timestamp: Local::now().naive_local(),
author: &post.author.trim(),
email: &post.email.trim(),
title: &post.title.trim(),
content: &post.content.trim(),
};
match post.insert(&conn) {
Ok(_) => {
info!("New post from {}", remote_address);
Ok(Redirect::to(uri!(index)))
}
Err(e) => {
error!("Could not create post from {}: {:?}", remote_address, e);
Err(Flash::error(
Redirect::to(uri!(create_form)),
"Could not create post.".to_string(),
))
}
}
}
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
@ -23,7 +80,8 @@ fn index() -> &'static str {
fn main() {
rocket::ignite()
.mount("/", routes![index, create_form])
.mount("/", routes![index, create_form, create_post])
.attach(DbCon::fairing())
.attach(Template::fairing())
.launch();
}

View file

@ -0,0 +1,20 @@
use crate::schema::posts::{self, dsl::posts as table_posts};
use chrono::NaiveDateTime;
use diesel::{QueryResult, RunQueryDsl};
#[derive(Insertable)]
#[table_name = "posts"]
pub struct NewPost<'a> {
pub parent: Option<&'a i32>,
pub timestamp: NaiveDateTime,
pub author: &'a str,
pub email: &'a str,
pub title: &'a str,
pub content: &'a str,
}
impl NewPost<'_> {
pub fn insert(&self, conn: &diesel::SqliteConnection) -> QueryResult<usize> {
diesel::insert_into(table_posts).values(self).execute(conn)
}
}

View file

@ -0,0 +1,11 @@
table! {
posts (id) {
id -> Integer,
parent -> Nullable<Integer>,
timestamp -> Timestamp,
author -> Text,
email -> Text,
title -> Text,
content -> Text,
}
}

View file

@ -3,9 +3,6 @@
{% block content %}
<h1>simple text board</h1>
<h2>create a post</h2>
{% if flash %}
<p>{{ flash }}</p>
{% endif %}
<form action="/create" method="post" accept-charset="utf-8">
<label for="author">author name</label>
<input type="text" placeholder="author" name="author" required>
@ -17,9 +14,9 @@
<input type="text" placeholder="title" name="title" required>
<label for="content">content</label>
<textarea type="text" placeholder="content" name="content" rows="8" cols="50" required>
</textarea>
<textarea type="text" placeholder="content" name="content" rows="8" cols="50" required></textarea>
<button type="submit">create</button>
{% if flash %}<p>{{ flash }}</p>{% endif %}
</form>
{% endblock content %}