Calendar (WIP)

This commit is contained in:
Greg Burri 2025-01-29 14:37:25 +01:00
parent 9d3f9e9c60
commit 79a0aeb1b8
24 changed files with 613 additions and 231 deletions

View file

@ -1,10 +1,9 @@
use chrono::prelude::*;
use chrono::{prelude::*, Days};
use common::ron_api::Difficulty;
use itertools::Itertools;
use super::{Connection, DBError, Result};
use crate::data::model;
use common::ron_api::Difficulty;
use crate::{data::model, user_authentication};
impl Connection {
/// Returns all the recipe titles where recipe is written in the given language.
@ -106,11 +105,10 @@ SELECT COUNT(*)
FROM [Recipe]
INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
WHERE [Group].[id] IN ({}) AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
WHERE [Group].[id] IN ({}) AND ([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1))
"#,
params
);
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
for id in group_ids {
query = query.bind(id);
@ -147,11 +145,10 @@ FROM [Recipe]
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 ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
WHERE [Step].[id] IN ({}) AND ([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1))
"#,
params
);
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
for id in steps_ids {
query = query.bind(id);
@ -199,11 +196,10 @@ 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
([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1))
"#,
params
);
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
for id in ingredients_ids {
query = query.bind(id);
@ -755,6 +751,73 @@ VALUES ($1, $2)
Ok(())
}
pub async fn add_schedule_recipe(
&self,
user_id: i64,
recipe_id: i64,
date: NaiveDate,
servings: u32,
) -> Result<()> {
sqlx::query(
r#"
INSERT INTO [RecipeScheduled] (user_id, recipe_id, date, servings)
VALUES ($1, $2, $3, $4)
"#,
)
.bind(user_id)
.bind(recipe_id)
.bind(date)
.bind(servings)
.execute(&self.pool)
.await
.map(|_| ())
.map_err(DBError::from)
}
pub async fn remove_scheduled_recipe(
&self,
user_id: i64,
recipe_id: i64,
date: NaiveDate,
) -> Result<()> {
sqlx::query(
r#"
DELETE FROM [RecipeScheduled]
WHERE [user_id] = $1 AND [recipe_id] = $2 AND [date] = $3
"#,
)
.bind(user_id)
.bind(recipe_id)
.bind(date)
.execute(&self.pool)
.await
.map(|_| ())
.map_err(DBError::from)
}
pub async fn get_scheduled_recipes(
&self,
user_id: i64,
start_date: NaiveDate,
end_date: NaiveDate,
) -> Result<Vec<(NaiveDate, String, i64)>> {
sqlx::query_as(
r#"
SELECT [date], [Recipe].[title], [Recipe].[id], [RecipeScheduled].[date]
FROM [RecipeScheduled]
INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeScheduled].[recipe_id]
WHERE [RecipeScheduled].[user_id] = $1 AND [date] >= $2 AND [date] <= $3
ORDER BY [date]
"#,
)
.bind(user_id)
.bind(start_date)
.bind(end_date)
.fetch_all(&self.pool)
.await
.map_err(DBError::from)
}
}
#[cfg(test)]
@ -884,4 +947,83 @@ VALUES
Ok(())
}
#[tokio::test]
async fn schedule_recipe() -> Result<()> {
let connection = Connection::new_in_memory().await?;
let user_id = create_a_user(&connection).await?;
let recipe_id_1 = connection.create_recipe(user_id).await?;
connection.set_recipe_title(recipe_id_1, "recipe 1").await?;
let recipe_id_2 = connection.create_recipe(user_id).await?;
connection.set_recipe_title(recipe_id_2, "recipe 2").await?;
let today = NaiveDate::from_ymd_opt(2025, 1, 23).unwrap();
let yesterday = today - Days::new(1);
let tomorrow = today + Days::new(1);
connection
.add_schedule_recipe(user_id, recipe_id_1, today, 4)
.await?;
connection
.add_schedule_recipe(user_id, recipe_id_2, yesterday, 4)
.await?;
connection
.add_schedule_recipe(user_id, recipe_id_1, tomorrow, 4)
.await?;
assert_eq!(
connection
.get_scheduled_recipes(user_id, today, today)
.await?,
vec![(
NaiveDate::from_ymd_opt(2025, 1, 23).unwrap(),
"recipe 1".to_string(),
1
)]
);
assert_eq!(
connection
.get_scheduled_recipes(user_id, yesterday, tomorrow)
.await?,
vec![
(
NaiveDate::from_ymd_opt(2025, 1, 22).unwrap(),
"recipe 2".to_string(),
2
),
(
NaiveDate::from_ymd_opt(2025, 1, 23).unwrap(),
"recipe 1".to_string(),
1
),
(
NaiveDate::from_ymd_opt(2025, 1, 24).unwrap(),
"recipe 1".to_string(),
1
)
]
);
connection
.remove_scheduled_recipe(user_id, recipe_id_1, today)
.await?;
connection
.remove_scheduled_recipe(user_id, recipe_id_2, yesterday)
.await?;
connection
.remove_scheduled_recipe(user_id, recipe_id_1, tomorrow)
.await?;
assert_eq!(
connection
.get_scheduled_recipes(user_id, yesterday, tomorrow)
.await?,
vec![]
);
Ok(())
}
}

View file

@ -1,5 +1,5 @@
use chrono::{prelude::*, Duration};
use rand::distributions::{Alphanumeric, DistString};
use rand::distr::{Alphanumeric, SampleString};
use sqlx::Sqlite;
use super::{Connection, DBError, Result};
@ -57,7 +57,7 @@ pub enum ResetPasswordResult {
}
fn generate_token() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), consts::TOKEN_SIZE)
Alphanumeric.sample_string(&mut rand::rng(), consts::TOKEN_SIZE)
}
impl Connection {