Add tables for shopping list and planner
authorGreg Burri <greg.burri@gmail.com>
Tue, 21 Jan 2025 18:27:06 +0000 (19:27 +0100)
committerGreg Burri <greg.burri@gmail.com>
Tue, 21 Jan 2025 18:27:06 +0000 (19:27 +0100)
Cargo.lock
backend/Cargo.toml
backend/sql/data_test.sql
backend/sql/version_1.sql
backend/src/data/db/recipe.rs
backend/src/html_templates.rs
backend/src/services/ron.rs
backend/translation.ron

index 06c8034..5b745be 100644 (file)
@@ -149,11 +149,11 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
 name = "axum"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
+checksum = "efea76243612a2436fb4074ba0cf3ba9ea29efdeb72645d8fc63f116462be1de"
 dependencies = [
- "axum-core 0.5.0",
+ "axum-core",
  "axum-macros",
  "bytes",
  "form_urlencoded",
@@ -184,32 +184,12 @@ dependencies = [
 
 [[package]]
 name = "axum-core"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
-dependencies = [
- "async-trait",
- "bytes",
- "futures-util",
- "http 1.2.0",
- "http-body",
- "http-body-util",
- "mime",
- "pin-project-lite",
- "rustversion",
- "sync_wrapper",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "axum-core"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
+checksum = "eab1b0df7cded837c40dacaa2e1c33aa17c84fc3356ae67b5645f1e83190753e"
 dependencies = [
  "bytes",
- "futures-util",
+ "futures-core",
  "http 1.2.0",
  "http-body",
  "http-body-util",
@@ -224,12 +204,12 @@ dependencies = [
 
 [[package]]
 name = "axum-extra"
-version = "0.10.0"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b"
+checksum = "543f0799d22486525744f06a3580b64f3e51d97aba73ea0e09040969c0034722"
 dependencies = [
  "axum",
- "axum-core 0.5.0",
+ "axum-core",
  "bytes",
  "cookie",
  "futures-util",
@@ -392,9 +372,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.26"
+version = "4.5.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
+checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -402,9 +382,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.26"
+version = "4.5.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
+checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
 dependencies = [
  "anstream",
  "anstyle",
@@ -1393,9 +1373,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.7.0"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
+checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
 dependencies = [
  "equivalent",
  "hashbrown 0.15.2",
@@ -1919,7 +1899,6 @@ dependencies = [
  "rand",
  "rand_core",
  "rinja",
- "rinja_axum",
  "ron",
  "serde",
  "sqlx",
@@ -2013,17 +1992,6 @@ dependencies = [
  "rinja_derive",
 ]
 
-[[package]]
-name = "rinja_axum"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc64d77bb950f6498d0fc64b028d168fcb4e56ac31b66a8ae05f64d3b0c218b6"
-dependencies = [
- "axum-core 0.4.5",
- "http 1.2.0",
- "rinja",
-]
-
 [[package]]
 name = "rinja_derive"
 version = "0.3.5"
@@ -2202,9 +2170,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.136"
+version = "1.0.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "336a0c23cf42a38d9eaa7cd22c7040d04e1228a19a933890805ffd00a16437d2"
+checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
 dependencies = [
  "itoa",
  "memchr",
index cf5db85..a55c0e1 100644 (file)
@@ -8,7 +8,7 @@ edition = "2021"
 common = { path = "../common" }
 
 axum = { version = "0.8", features = ["macros"] }
-axum-extra = { version = "0.10", features = ["cookie"] }
+axum-extra = { version = "0.11", features = ["cookie"] }
 tokio = { version = "1", features = ["full"] }
 tower = { version = "0.5", features = ["util"] }
 tower-http = { version = "0.6", features = ["fs", "trace"] }
@@ -27,8 +27,7 @@ clap = { version = "4", features = ["derive"] }
 
 sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "chrono"] }
 
-rinja = { version = "0.3", features = ["with-axum"] }
-rinja_axum = "0.3"
+rinja = { version = "0.3" }
 
 argon2 = { version = "0.5", features = ["default", "std"] }
 rand_core = { version = "0.6", features = ["std"] }
index c68174b..c2ec78c 100644 (file)
@@ -1,8 +1,9 @@
-INSERT INTO [User] ([id], [email], [name], [creation_datetime], [password], [validation_token_datetime], [validation_token])
+INSERT INTO [User] ([id], [email], [name], [is_admin], [creation_datetime], [password], [validation_token_datetime], [validation_token])
 VALUES (
     1,
     'paul@atreides.com',
     'Paul',
+    TRUE,
     '2025-01-07T10:41:05.697884837+00:00',
     '$argon2id$v=19$m=4096,t=4,p=2$l1fAMRc0VfkNzqpEfFEReg$/gsUsY2aML8EbKjPeCxucenxkxhiFSXDmizWZPLvNuo',
     0,
index 46cbd1d..0100ee3 100644 (file)
@@ -151,19 +151,72 @@ CREATE TABLE [Ingredient] (
     [step_id] INTEGER NOT NULL,
 
     [name] TEXT NOT NULL DEFAULT '',
-    [comment] TEXT NOT NULL DEFAULT '',
     [quantity_value] REAL,
     [quantity_unit] TEXT NOT NULL DEFAULT '',
 
+    [comment] TEXT NOT NULL DEFAULT '',
+
     FOREIGN KEY([step_id]) REFERENCES [Step]([id]) ON DELETE CASCADE
 ) STRICT;
 
 CREATE INDEX [Ingredient_order_index] ON [Ingredient]([order]);
 
--- Table not strict because [value] can story any type of data.
+CREATE TABLE [RecipeScheduled] (
+    [id] INTEGER PRIMARY KEY,
+    [user_id] INTEGER NOT NULL,
+    [recipe_id] INTEGER NOT NULL,
+    [date] TEXT NOT NULL,
+    [servings] INTEGER, -- If NULL use [recipe].[servings].
+
+    FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE CASCADE,
+    FOREIGN KEY([recipe_id]) REFERENCES [Recipe]([id]) ON DELETE CASCADE
+);
+
+CREATE TABLE [ShoppingEntry] (
+    [id] INTEGER PRIMARY KEY,
+    [user_id] INTEGER NOT NULL,
+    -- The linkded ingredient can be deleted or a custom entry can be manually added.
+    -- In both cases [name], [quantity_value] and [quantity_unit] are used to display
+    -- the entry instead of [Ingredient] data.
+    [ingredient_id] INTEGER,
+    [is_checked] INTEGER NOT NULL DEFAULT FALSE,
+
+    [name] TEXT NOT NULL DEFAULT '',
+    [quantity_value] REAL,
+    [quantity_unit] TEXT NOT NULL DEFAULT '',
+    [servings] INTEGER,
+
+    FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE CASCADE,
+    FOREIGN KEY([ingredient_id]) REFERENCES [Ingredient]([id]) ON DELETE SET NULL
+);
+
+-- When an ingredient is deleted, its values are copied to any shopping entry
+-- that referenced it.
+CREATE TRIGGER [Ingredient_trigger_delete]
+BEFORE DELETE
+ON [Ingredient]
+BEGIN
+    UPDATE [ShoppingEntry]
+    SET
+        [name] = OLD.[name],
+        [quantity_value] = OLD.[quantity_value],
+        [quantity_unit] = OLD.[quantity_unit],
+        [servings] = (
+               SELECT [servings]
+               FROM [Recipe]
+               INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
+               INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
+               WHERE [Step].[id] = OLD.[step_id]
+       )
+    WHERE [ingredient_id] = OLD.[id];
+END;
+
 CREATE TABLE [Settings] (
     [name] TEXT NOT NULL PRIMARY KEY,
+
+    -- Value can by anything that can be read from a text by
+    -- implementing the trait 'std::str::FromStr'.
     [value] TEXT NOT NULL
-);
+) STRICT;
 
-INSERT INTO [Settings] ([name], [value]) VALUES ('new_user_registration_enabled', TRUE);
+INSERT INTO [Settings] ([name], [value]) VALUES ('new_user_registration_enabled', 'true');
index e59b064..d1e1a8f 100644 (file)
@@ -64,8 +64,8 @@ ORDER BY [title]
             r#"
 SELECT COUNT(*) = 1
 FROM [Recipe]
-INNER JOIN [User] ON [User].id = [Recipe].user_id
-WHERE [Recipe].[id] = $1 AND ([is_admin] OR [user_id] = $2)
+INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
+WHERE [Recipe].[id] = $1 AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
             "#,
         )
         .bind(recipe_id)
@@ -80,9 +80,9 @@ WHERE [Recipe].[id] = $1 AND ([is_admin] OR [user_id] = $2)
             r#"
 SELECT COUNT(*) = 1
 FROM [Recipe]
-INNER JOIN [User] ON [User].id = [Recipe].user_id
+INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
 INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
-WHERE [Group].[id] = $1 AND ([is_admin] OR [user_id] = $2)
+WHERE [Group].[id] = $1 AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
             "#,
         )
         .bind(group_id)
@@ -104,9 +104,9 @@ WHERE [Group].[id] = $1 AND ([is_admin] OR [user_id] = $2)
             r#"
 SELECT COUNT(*)
 FROM [Recipe]
-INNER JOIN [User] ON [User].id = [Recipe].user_id
+INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
 INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
-WHERE [Group].[id] IN ({}) AND ([is_admin] OR [user_id] = $2)
+WHERE [Group].[id] IN ({}) AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
             "#,
             params
         );
@@ -123,10 +123,10 @@ WHERE [Group].[id] IN ({}) AND ([is_admin] OR [user_id] = $2)
             r#"
 SELECT COUNT(*) = 1
 FROM [Recipe]
-INNER JOIN [User] ON [User].id = [Recipe].user_id
+INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
 INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
 INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
-WHERE [Step].[id] = $1 AND ([is_admin] OR [user_id] = $2)
+WHERE [Step].[id] = $1 AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
             "#,
         )
         .bind(step_id)
@@ -144,10 +144,10 @@ WHERE [Step].[id] = $1 AND ([is_admin] OR [user_id] = $2)
             r#"
 SELECT COUNT(*)
 FROM [Recipe]
-INNER JOIN [User] ON [User].id = [Recipe].user_id
+INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
 INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
 INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
-WHERE [Step].[id] IN ({}) AND ([is_admin] OR [user_id] = $2)
+WHERE [Step].[id] IN ({}) AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
             "#,
             params
         );
@@ -168,11 +168,11 @@ WHERE [Step].[id] IN ({}) AND ([is_admin] OR [user_id] = $2)
             r#"
 SELECT COUNT(*)
 FROM [Recipe]
-INNER JOIN [User] ON [User].id = [Recipe].user_id
+INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
 INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
 INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
 INNER JOIN [Ingredient] ON [Ingredient].[step_id] = [Step].[id]
-WHERE [Ingredient].[id] = $1 AND ([is_admin] OR [user_id] = $2)
+WHERE [Ingredient].[id] = $1 AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
             "#,
         )
         .bind(ingredient_id)
@@ -194,11 +194,12 @@ WHERE [Ingredient].[id] = $1 AND ([is_admin] OR [user_id] = $2)
             r#"
 SELECT COUNT(*)
 FROM [Recipe]
-INNER JOIN [User] ON [User].id = [Recipe].user_id
+INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
 INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
 INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
 INNER JOIN [Ingredient] ON [Ingredient].[step_id] = [Step].[id]
-WHERE [Ingredient].[id] IN ({}) AND ([is_admin] OR [user_id] = $2)
+WHERE [Ingredient].[id] IN ({}) AND
+    ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
             "#,
             params
         );
@@ -681,6 +682,8 @@ VALUES ($1, $2)
         .execute(&mut *tx)
         .await?;
 
+        tx.commit().await?;
+
         Ok(db_result.last_insert_rowid())
     }
 
index a1fb570..3ee4ff1 100644 (file)
@@ -1,4 +1,4 @@
-use rinja_axum::Template;
+use rinja::Template;
 
 use crate::{
     data::model,
index 900152b..a6c6238 100644 (file)
@@ -522,7 +522,6 @@ pub async fn add_ingredient(
 ) -> Result<impl IntoResponse> {
     check_user_rights_recipe_step(&connection, &user, ron.id).await?;
     let id = connection.add_recipe_ingredient(ron.id).await?;
-
     Ok(ron_response(StatusCode::OK, common::ron_api::Id { id }))
 }
 
index cf79c8f..835f018 100644 (file)
             (RecipeDifficultyHard, "Difficile"),
             (RecipeTags, "Tags"),
             (RecipeLanguage, "Langue"),
-            (RecipeIsPublished, "Est publiĆ©"),
+            (RecipeIsPublished, "Est publiĆ©e"),
             (RecipeDelete, "Supprimer la recette"),
             (RecipeAddAGroup, "Ajouter un groupe"),
             (RecipeRemoveGroup, "Supprimer le groupe"),