40
loading...
This website collects cookies to deliver better user experience
api
and allow the users to list, create, update and delete dinos
.deps
we need, in this case we will using tera
and tide-tera, the last one exposes an extension trait that adds two functions tera
:[dependencies]
...
tera = "1.5.0"
tide-tera = "0.2.2"
src
directory )mkdir templates
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}{% endblock title %}</title>
<meta charset="utf-8">
<meta name="description" content="Tide basic CRUD">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta property="og:title" content="Tide basic CRUD" />
{% block additional_head %}
{% endblock additional_head %}
</head>
<body>
{% block content %}
{% endblock content %}
</body>
</html>
{% extends "layout.html" %}
{% block title %}
{{title}}
{% endblock title %}
{% block content %}
<h2> Hi there!</div>
{% endblock content %}
tera
syntax you can check the docs, but in a nutshell:{{
and }}
for expressions{%
or {%-
and %}
or -%}
for statements{#
and #}
for commentsblock / endblock
allow to extends
.tide
server and implement the serve ( and rendering ) logic.deps
...
use tera::Tera;
use tide_tera::prelude::*;
State
struct to hold an instance of tera.#[derive(Clone, Debug)]
struct State {
db_pool: PgPool,
tera: Tera
}
server
fn to add the instantiation of tera
and modify the endpoint to render the index.html templateasync fn server(db_pool: PgPool) -> Server<State> {
let mut tera = Tera::new("templates/**/*").expect("Error parsing templates directory");
tera.autoescape_on(vec!["html"]);
let state = State { db_pool, tera };
let mut app = tide::with_state(state);
// index page
app.at("/").get( |req: tide::Request<State> | async move {
let tera = req.state().tera.clone();
tera.render_response("index.html", &context! { "title" => String::from("Tide basic CRUD") })
} );
dinos
we have and allow users to create, update and delete.dinos
stored in our database, so the first thing we need to do is get that list to then inject in the context of our template// index page
app.at("/").get( |req: tide::Request<State> | async move {
let tera = req.state().tera.clone();
let db_pool = req.state().db_pool.clone();
let rows = query_as!(
Dino,
r#"
SELECT id, name, weight, diet from dinos
"#
)
.fetch_all(&db_pool)
.await?;
tera.render_response("index.html", &context! {
"title" => String::from("Tide basic CRUD"),
"dinos" => rows
})
} );
{% block content %}
{% if dinos %}
<table class="u-full-width">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Weight</th>
<th>Diet</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{%for dino in dinos%}
<tr>
<td>{{dino.id}}</td>
<td>{{dino.name}}</td>
<td>{{dino.weight}}</td>
<td>{{dino.diet}}</td>
<td><a href="/dinos/{{dino.id}}/edit"> Edit </a></td>
<td><a href="/dinos/{{dino.id}}/delete"> Delete </a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<a href="/dinos/new">Create new Dino</a>
{% endblock content %}
Tide
have a handy way to serve static files, you can define the path
and the directory
you want to serve and those files are server statically from disk.app.at("/public").serve_dir("./public/").expect("Invalid static file directory");
public
directory, so we need to create that directory and place the static assets we want to serve there.mkdir -p public/{js,css,images}
dino
.edit
will be pre-populated with the dino
information and each action will have it's own route/dinos/new
/dinos/{id}/edit
{% extends "layout.html" %}
{% block content %}
<form>
<input id="id" name="id" type="hidden" value="{% if dino %} dino.id {% endif %}">
<div class="row">
<div class="ten columns">
<label for="name">Name</label>
<input class="u-full-width" id="name" name="name" type="text" placeholder="T-Rex" value="{% if dino %} dino.name {% endif %}">
</div>
</div>
<div class="row">
<div class="ten columns">
<label for="weight">Weight</label>
<input class="u-full-width" name="weight" id="weight" type="text" placeholder="" value="{% if dino %} dino.weight {% endif %}">
</div>
</div>
<div class="row">
<div class="ten columns">
<label for="diet">Diet</label>
<input class="u-full-width" name="diet" id="diet" type="text" placeholder="" value="{% if dino %} dino.diet {% endif %}">
</div>
</div>
<input class="button-primary" type="submit" value="Submit"> <a class="button" href="/">Cancel</a>
</form>
{% endblock %}
pre-populated
only if the dino
info is present in the context. We could use some macros to generate the fields but works ok for now.// new dino
app.at("/dinos/new").get( |req: tide::Request<State> | async move {
let tera = req.state().tera.clone();
tera.render_response("form.html", &context! {
"title" => String::from("Create new dino")
})
} );
// edit dino
app.at("/dinos/:id/edit").get( |req: tide::Request<State> | async move {
let tera = req.state().tera.clone();
let db_pool = req.state().db_pool.clone();
let id: Uuid = Uuid::parse_str(req.param("id")?).unwrap();
let row = query_as!(
Dino,
r#"
SELECT id, name, weight, diet from dinos
WHERE id = $1
"#,
id
)
.fetch_optional(&db_pool)
.await?;
let res = match row {
None => Response::new(404),
Some(row) => {
let mut r = Response::new(200);
let b = tera.render_body("form.html", &context! {
"title" => String::from("Edit dino"),
"dino" => row
})?;
r.set_body(b);
r
}
};
Ok(res)
} );
Nice!, but we are starting to have some duplicate code between the endpoints for the rest api and the endpoint for this pages. For the moment let's focus in the functionality and we will refactor this in the next post
.
dinos
:)post
we will be refactoring some of the dup
code and adding a new abstraction layer.