Add support for meals (#1)
Reviewed-on: #1 Co-authored-by: Simon Bernier St-Pierre <git.sbstp.ca@gmail.com>
This commit was merged in pull request #1.
This commit is contained in:
+173
-7
@@ -1,9 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::domain::{DomainError, DomainResult, GroceryList, Item, SessionUser, User};
|
||||
use crate::domain::{
|
||||
DomainError, DomainResult, GroceryList, Item, Meal, SessionUser, User,
|
||||
};
|
||||
use crate::ports::{
|
||||
CategoryRepository, InvitationRepository, ItemRepository, ListRepository, PasswordHasher,
|
||||
RealtimeNotifier, SessionRepository, TokenGenerator, UserRepository,
|
||||
CategoryRepository, InvitationRepository, ItemRepository, ListRepository, MealIngredientRepository,
|
||||
MealRepository, NewItem, PasswordHasher, RealtimeNotifier, SessionRepository, TokenGenerator,
|
||||
UserRepository,
|
||||
};
|
||||
use crate::sqlite::SqliteDatabase;
|
||||
|
||||
@@ -187,10 +190,10 @@ impl ListService {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn categories(&self, list_id: i64) -> DomainResult<Vec<crate::domain::Category>> {
|
||||
pub async fn categories(&self) -> DomainResult<Vec<crate::domain::Category>> {
|
||||
let categories = Arc::clone(&self.categories);
|
||||
self.db
|
||||
.run(move |txn| Box::pin(async move { categories.categories(txn, list_id).await }))
|
||||
.run(move |txn| Box::pin(async move { categories.categories(txn).await }))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -270,12 +273,175 @@ impl ListService {
|
||||
Ok(revision)
|
||||
}
|
||||
|
||||
pub async fn create_category(&self, list_id: i64, name: String) -> DomainResult<i64> {
|
||||
pub async fn create_category(&self, name: String) -> DomainResult<i64> {
|
||||
let categories = Arc::clone(&self.categories);
|
||||
self.db
|
||||
.run(move |txn| Box::pin(async move { categories.create_category(txn, name).await }))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MealService {
|
||||
db: SqliteDatabase,
|
||||
meals: Arc<dyn MealRepository>,
|
||||
ingredients: Arc<dyn MealIngredientRepository>,
|
||||
lists: Arc<dyn ListRepository>,
|
||||
items: Arc<dyn ItemRepository>,
|
||||
realtime: Arc<dyn RealtimeNotifier>,
|
||||
}
|
||||
|
||||
impl MealService {
|
||||
pub fn new(
|
||||
db: SqliteDatabase,
|
||||
meals: Arc<dyn MealRepository>,
|
||||
ingredients: Arc<dyn MealIngredientRepository>,
|
||||
lists: Arc<dyn ListRepository>,
|
||||
items: Arc<dyn ItemRepository>,
|
||||
realtime: Arc<dyn RealtimeNotifier>,
|
||||
) -> Self {
|
||||
Self {
|
||||
db,
|
||||
meals,
|
||||
ingredients,
|
||||
lists,
|
||||
items,
|
||||
realtime,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_meal(&self, name: String, description: String) -> DomainResult<Meal> {
|
||||
let meals = Arc::clone(&self.meals);
|
||||
self.db
|
||||
.run(move |txn| Box::pin(async move { meals.create_meal(txn, name, description).await }))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_meal(&self, meal_id: i64) -> DomainResult<Option<Meal>> {
|
||||
let meals = Arc::clone(&self.meals);
|
||||
self.db
|
||||
.run(move |txn| Box::pin(async move { meals.get_meal(txn, meal_id).await }))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_meals(&self) -> DomainResult<Vec<Meal>> {
|
||||
let meals = Arc::clone(&self.meals);
|
||||
self.db
|
||||
.run(move |txn| Box::pin(async move { meals.list_meals(txn).await }))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_meal(
|
||||
&self,
|
||||
meal_id: i64,
|
||||
name: String,
|
||||
description: String,
|
||||
) -> DomainResult<()> {
|
||||
let meals = Arc::clone(&self.meals);
|
||||
self.db
|
||||
.run(move |txn| {
|
||||
Box::pin(async move { meals.update_meal(txn, meal_id, name, description).await })
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_meal(&self, meal_id: i64) -> DomainResult<()> {
|
||||
let meals = Arc::clone(&self.meals);
|
||||
self.db
|
||||
.run(move |txn| Box::pin(async move { meals.delete_meal(txn, meal_id).await }))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn add_ingredient(
|
||||
&self,
|
||||
meal_id: i64,
|
||||
name: String,
|
||||
quantity: String,
|
||||
note: String,
|
||||
category_id: Option<i64>,
|
||||
) -> DomainResult<i64> {
|
||||
let ingredients = Arc::clone(&self.ingredients);
|
||||
self.db
|
||||
.run(move |txn| {
|
||||
Box::pin(async move {
|
||||
ingredients
|
||||
.add_ingredient(txn, meal_id, name, quantity, note, category_id)
|
||||
.await
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_ingredient(
|
||||
&self,
|
||||
meal_id: i64,
|
||||
ingredient_id: i64,
|
||||
name: String,
|
||||
quantity: String,
|
||||
note: String,
|
||||
category_id: Option<i64>,
|
||||
) -> DomainResult<()> {
|
||||
let ingredients = Arc::clone(&self.ingredients);
|
||||
self.db
|
||||
.run(move |txn| {
|
||||
Box::pin(async move {
|
||||
ingredients
|
||||
.update_ingredient(
|
||||
txn,
|
||||
meal_id,
|
||||
ingredient_id,
|
||||
name,
|
||||
quantity,
|
||||
note,
|
||||
category_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_ingredient(&self, meal_id: i64, ingredient_id: i64) -> DomainResult<()> {
|
||||
let ingredients = Arc::clone(&self.ingredients);
|
||||
self.db
|
||||
.run(move |txn| {
|
||||
Box::pin(async move {
|
||||
ingredients
|
||||
.delete_ingredient(txn, meal_id, ingredient_id)
|
||||
.await
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Expands a meal's ingredients into items on a list in one unit of work,
|
||||
/// bumping the list revision exactly once.
|
||||
pub async fn add_meal_to_list(&self, meal_id: i64, list_id: i64) -> DomainResult<i64> {
|
||||
let meals = Arc::clone(&self.meals);
|
||||
let lists = Arc::clone(&self.lists);
|
||||
let items = Arc::clone(&self.items);
|
||||
let revision = self
|
||||
.db
|
||||
.run(move |txn| {
|
||||
Box::pin(async move { categories.create_category(txn, list_id, name).await })
|
||||
Box::pin(async move {
|
||||
let meal = meals
|
||||
.get_meal(txn, meal_id)
|
||||
.await?
|
||||
.ok_or(DomainError::NotFound)?;
|
||||
if lists.get_list(txn, list_id).await?.is_none() {
|
||||
return Err(DomainError::NotFound);
|
||||
}
|
||||
let new_items = meal
|
||||
.ingredients
|
||||
.into_iter()
|
||||
.map(|ingredient| NewItem {
|
||||
name: ingredient.name,
|
||||
quantity: ingredient.quantity,
|
||||
note: ingredient.note,
|
||||
category_id: ingredient.category_id,
|
||||
})
|
||||
.collect();
|
||||
items.add_items_bulk(txn, list_id, new_items).await
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
self.realtime.publish_list_changed(list_id, revision).await;
|
||||
|
||||
Reference in New Issue
Block a user