This commit is contained in:
Greg Burri 2023-04-10 09:35:10 +02:00
parent e7d2f8f6c7
commit 57d7e7a3ce
17 changed files with 608 additions and 500 deletions

1
.gitignore vendored
View file

@ -10,6 +10,7 @@ backend/file.db
conf.ron conf.ron
*.wasm *.wasm
*.asml.bak
node_modules node_modules
pkg pkg
package-lock.json package-lock.json

1020
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,9 @@
* Describe the use cases. * Describe the use cases.
* Define the UI (mockups). * Define the UI (mockups).
* Two CSS: one for desktop and one for mobile * Two CSS: one for desktop and one for mobile
* Use CSS flex/grid to define a good design/layout
* Define the logic behind each page and action. * Define the logic behind each page and action.
* Add support to language into db model. * Add support to translations into db model.
[ok] Change all id to i64 [ok] Change all id to i64
[ok] Check cookie lifetime -> Session by default [ok] Check cookie lifetime -> Session by default

View file

@ -19,7 +19,7 @@ itertools = "0.10"
clap = {version = "4", features = ["derive"]} clap = {version = "4", features = ["derive"]}
log = "0.4" log = "0.4"
env_logger = "0.9" env_logger = "0.10"
r2d2_sqlite = "0.21" # Connection pool with rusqlite (SQLite access). r2d2_sqlite = "0.21" # Connection pool with rusqlite (SQLite access).
r2d2 = "0.8" r2d2 = "0.8"
@ -27,10 +27,10 @@ rusqlite = {version = "0.28", features = ["bundled", "chrono"]}
futures = "0.3" # Needed by askam with the feature 'with-actix-web'. futures = "0.3" # Needed by askam with the feature 'with-actix-web'.
askama = {version = "0.11", features = ["with-actix-web", "mime", "mime_guess", "markdown"]} askama = {version = "0.12", features = ["with-actix-web", "mime", "mime_guess", "markdown"]}
askama_actix = "0.13" askama_actix = "0.14"
argon2 = {version = "0.4", features = ["default", "std"]} argon2 = {version = "0.5", features = ["default", "std"]}
rand_core = {version = "0.6", features = ["std"]} rand_core = {version = "0.6", features = ["std"]}
rand = "0.8" rand = "0.8"

View file

@ -117,6 +117,7 @@ impl Connection {
fn get(&self) -> Result<PooledConnection<SqliteConnectionManager>> { fn get(&self) -> Result<PooledConnection<SqliteConnectionManager>> {
let con = self.pool.get()?; let con = self.pool.get()?;
// ('foreign_keys' is ON by default).
con.pragma_update(None, "synchronous", "NORMAL")?; con.pragma_update(None, "synchronous", "NORMAL")?;
Ok(con) Ok(con)
} }
@ -124,13 +125,12 @@ impl Connection {
/// Called after the connection has been established for creating or updating the database. /// Called after the connection has been established for creating or updating the database.
/// The 'Version' table tracks the current state of the database. /// The 'Version' table tracks the current state of the database.
fn create_or_update_db(&self) -> Result<()> { fn create_or_update_db(&self) -> Result<()> {
// Check the Database version.
let mut con = self.get()?; let mut con = self.get()?;
con.pragma_update(None, "journal_mode", "WAL")?; con.pragma_update(None, "journal_mode", "WAL")?; // Note: use "WAL2" when available.
let tx = con.transaction()?; let tx = con.transaction()?;
// Version 0 corresponds to an empty database. // Check current database version. (Version 0 corresponds to an empty database).
let mut version = { let mut version = {
match tx.query_row( match tx.query_row(
"SELECT [name] FROM [sqlite_master] WHERE [type] = 'table' AND [name] = 'Version'", "SELECT [name] FROM [sqlite_master] WHERE [type] = 'table' AND [name] = 'Version'",
@ -456,11 +456,14 @@ impl Connection {
match con match con
.query_row( .query_row(
"SELECT [Recipe].[id] FROM [Recipe] "SELECT [Recipe].[id] FROM [Recipe]
INNER JOIN [Image] ON [Image].[recipe_id] = [Recipe].[id] LEFT JOIN [Image] ON [Image].[recipe_id] = [Recipe].[id]
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id] LEFT JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
WHERE [Recipe].[user_id] = ?1 WHERE [Recipe].[user_id] = ?1
AND [Recipe].[estimate_time] = NULL AND [Recipe].[title] = ''
AND [Recipe].[description] = NULL", AND [Recipe].[estimate_time] IS NULL
AND [Recipe].[description] = ''
AND [Image].[id] IS NULL
AND [Group].[id] IS NULL",
[user_id], [user_id],
|r| Ok(r.get::<&str, i64>("id")?), |r| Ok(r.get::<&str, i64>("id")?),
) )

View file

@ -50,6 +50,9 @@ async fn main() -> std::io::Result<()> {
.service(services::sign_out) .service(services::sign_out)
.service(services::view_recipe) .service(services::view_recipe)
.service(services::edit_recipe) .service(services::edit_recipe)
.service(services::new_recipe)
.service(services::webapi::set_recipe_title)
.service(services::webapi::set_recipe_description)
.service(fs::Files::new("/static", "static")) .service(fs::Files::new("/static", "static"))
.default_service(web::to(services::not_found)) .default_service(web::to(services::not_found))
}); });

View file

@ -18,7 +18,7 @@ use crate::{
email, model, utils, email, model, utils,
}; };
mod api; pub mod webapi;
///// UTILS ///// ///// UTILS /////
@ -255,7 +255,6 @@ pub async fn edit_recipe(
#[get("/recipe/new")] #[get("/recipe/new")]
pub async fn new_recipe( pub async fn new_recipe(
req: HttpRequest, req: HttpRequest,
path: web::Path<(i64,)>,
connection: web::Data<db::Connection>, connection: web::Data<db::Connection>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let user = match get_current_user(&req, connection.clone()).await { let user = match get_current_user(&req, connection.clone()).await {

View file

@ -17,6 +17,6 @@
</script> </script>
{% block body_container %}{% endblock %} {% block body_container %}{% endblock %}
<div class="footer-container">gburri - 2022</div> <footer class="footer-container">gburri - 2022</footer>
</body> </body>
</html> </html>

View file

@ -5,12 +5,14 @@
{% endmacro %} {% endmacro %}
{% block main_container %} {% block main_container %}
<div class="list"> <nav class="list">
<ul> <ul>
{% for (id, title) in recipes %} {% for (id, title) in recipes %}
<li> <li>
{% match current_recipe_id %} {% match current_recipe_id %}
{# Don't know how to avoid repetition: comparing (using '==' or .eq()) current_recipe_id.unwrap() and id doesn't work. Guards for match don't exist. {# Don't know how to avoid
repetition: comparing (using '==' or .eq()) current_recipe_id.unwrap() and id doesn't work.
Guards for match don't exist.
See: https://github.com/djc/askama/issues/752 #} See: https://github.com/djc/askama/issues/752 #}
{% when Some (current_id) %} {% when Some (current_id) %}
{% if current_id == id %} {% if current_id == id %}
@ -24,7 +26,7 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </nav>
<div class="content"> <div class="content">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>

View file

@ -1,7 +1,6 @@
{% extends "base_with_list.html" %} {% extends "base_with_list.html" %}
{% block content %} {% block content %}
<label for="title_field">Title</label> <label for="title_field">Title</label>
<input <input
id="title_field" id="title_field"
@ -12,4 +11,13 @@
autocomplete="title" autocomplete="title"
autofocus="autofocus" /> autofocus="autofocus" />
<label for="description_field">Description</label>
<input
id="title_field"
type="text"
name="title"
value="{{ current_recipe.description }}"
autocapitalize="none"
autocomplete="title"
autofocus="autofocus" />
{% endblock %} {% endblock %}

13
doc/recipes.service Normal file
View file

@ -0,0 +1,13 @@
[Unit]
Description=Recipes
After=network.target
[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/recipes
ExecStart=/home/pi/recipes/recipes
Restart=always
[Install]
WantedBy=multi-user.target

View file

@ -36,4 +36,4 @@ opt-level = "s"
lto = true lto = true
[package.metadata.wasm-pack.profile.release] [package.metadata.wasm-pack.profile.release]
wasm-opt = false wasm-opt = false

View file

@ -1,3 +1,3 @@
wasm-pack build --target web wasm-pack build --target web -- --features wee_alloc
cp pkg/frontend.js ../backend/static cp pkg/frontend.js ../backend/static
cp pkg/frontend_bg.wasm ../backend/static cp pkg/frontend_bg.wasm ../backend/static

View file

@ -1,6 +1,4 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::Document; use web_sys::Document;
pub fn edit_recipe(doc: &Document) { pub fn edit_recipe(doc: &Document) {}
}

View file

@ -1,5 +1,5 @@
mod utils;
mod handles; mod handles;
mod utils;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::console; use web_sys::console;
@ -11,7 +11,7 @@ use web_sys::console;
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen] #[wasm_bindgen]
extern { extern "C" {
fn alert(s: &str); fn alert(s: &str);
} }
@ -48,7 +48,7 @@ pub fn main() -> Result<(), JsValue> {
handles::edit_recipe(&document); handles::edit_recipe(&document);
let title_input = document.get_element_by_id("title_field").unwrap(); let title_input = document.get_element_by_id("title_field").unwrap();
}, }
_ => (), _ => (),
} }
@ -60,4 +60,4 @@ pub fn main() -> Result<(), JsValue> {
// body.append_child(&val)?; // body.append_child(&val)?;
Ok(()) Ok(())
} }

View file

@ -16,4 +16,4 @@ macro_rules! console_log {
// Note that this is using the `log` function imported above during // Note that this is using the `log` function imported above during
// `bare_bones` // `bare_bones`
($($t:tt)*) => (console::log_1(&format_args!($($t)*).to_string().into())) ($($t:tt)*) => (console::log_1(&format_args!($($t)*).to_string().into()))
} }