use async_trait::async_trait; use sqlx::SqliteConnection; use crate::domain::{ Category, DomainResult, GroceryList, Item, ListMeal, Meal, MealCategory, MealIngredient, Passkey, PresenceUser, RewardsCard, SessionUser, User, }; /// Repositories take `&mut SqliteConnection` (which a `Transaction` derefs to), /// so several repositories can commit together atomically within a single /// transaction coordinated by the unit of work. #[async_trait] pub trait UserRepository: Send + Sync { async fn create_user( &self, txn: &mut SqliteConnection, email: String, display_name: String, password_hash: String, ) -> DomainResult; async fn find_user_by_email( &self, txn: &mut SqliteConnection, email: String, ) -> DomainResult>; async fn find_user_by_handle( &self, txn: &mut SqliteConnection, user_handle: Vec, ) -> DomainResult>; async fn update_password_hash( &self, txn: &mut SqliteConnection, user_id: i64, password_hash: String, ) -> DomainResult<()>; async fn has_users(&self, txn: &mut SqliteConnection) -> DomainResult; } #[async_trait] pub trait PasskeyRepository: Send + Sync { async fn create_passkey( &self, txn: &mut SqliteConnection, user_id: i64, credential_id: String, credential: String, counter: i64, ) -> DomainResult; async fn find_by_credential_id( &self, txn: &mut SqliteConnection, credential_id: String, ) -> DomainResult>; async fn list_for_user( &self, txn: &mut SqliteConnection, user_id: i64, ) -> DomainResult>; async fn delete_passkey( &self, txn: &mut SqliteConnection, user_id: i64, passkey_id: i64, ) -> DomainResult<()>; } #[async_trait] pub trait SessionRepository: Send + Sync { async fn create_session( &self, txn: &mut SqliteConnection, user_id: i64, ) -> DomainResult<(String, String)>; async fn session_user( &self, txn: &mut SqliteConnection, session_token: String, ) -> DomainResult>; async fn delete_session( &self, txn: &mut SqliteConnection, session_token: String, ) -> DomainResult<()>; } #[async_trait] pub trait ListRepository: Send + Sync { async fn list_summaries(&self, txn: &mut SqliteConnection) -> DomainResult>; async fn list_archived_summaries( &self, txn: &mut SqliteConnection, ) -> DomainResult>; async fn create_list( &self, txn: &mut SqliteConnection, name: String, ) -> DomainResult; async fn get_list( &self, txn: &mut SqliteConnection, list_id: i64, ) -> DomainResult>; async fn set_archived( &self, txn: &mut SqliteConnection, list_id: i64, archived: bool, ) -> DomainResult<()>; } #[async_trait] pub trait CategoryRepository: Send + Sync { async fn categories(&self, txn: &mut SqliteConnection) -> DomainResult>; async fn create_category(&self, txn: &mut SqliteConnection, name: String) -> DomainResult; } /// A single item to insert in bulk, without a per-item revision bump. #[derive(Clone, Debug)] pub struct NewItem { pub name: String, pub quantity: String, pub note: String, pub category_id: Option, /// When set, links this item to the `list_meals` row it came from, so the /// item is removed together with that meal instance. pub list_meal_id: Option, } #[async_trait] pub trait ItemRepository: Send + Sync { async fn items(&self, txn: &mut SqliteConnection, list_id: i64) -> DomainResult>; async fn add_item( &self, txn: &mut SqliteConnection, list_id: i64, name: String, quantity: String, note: String, category_id: Option, ) -> DomainResult; async fn add_items_bulk( &self, txn: &mut SqliteConnection, list_id: i64, items: Vec, ) -> DomainResult; async fn set_item_checked( &self, txn: &mut SqliteConnection, list_id: i64, item_id: i64, checked: bool, ) -> DomainResult; async fn update_item( &self, txn: &mut SqliteConnection, list_id: i64, item_id: i64, name: String, quantity: String, note: String, category_id: Option, ) -> DomainResult; async fn delete_item( &self, txn: &mut SqliteConnection, list_id: i64, item_id: i64, ) -> DomainResult; } #[async_trait] pub trait ListMealRepository: Send + Sync { async fn list_meals( &self, txn: &mut SqliteConnection, list_id: i64, ) -> DomainResult>; async fn add_meal( &self, txn: &mut SqliteConnection, list_id: i64, meal_id: i64, name: String, ) -> DomainResult; async fn remove_meal( &self, txn: &mut SqliteConnection, list_id: i64, list_meal_id: i64, ) -> DomainResult; } #[async_trait] pub trait InvitationRepository: Send + Sync { async fn create_invitation( &self, txn: &mut SqliteConnection, created_by: i64, token: String, ) -> DomainResult; async fn invitation(&self, txn: &mut SqliteConnection, token: String) -> DomainResult; async fn accept_invitation( &self, txn: &mut SqliteConnection, token: String, ) -> DomainResult<()>; } #[async_trait] pub trait MealCategoryRepository: Send + Sync { async fn meal_categories(&self, txn: &mut SqliteConnection) -> DomainResult>; async fn create_meal_category( &self, txn: &mut SqliteConnection, name: String, ) -> DomainResult; async fn delete_meal_category( &self, txn: &mut SqliteConnection, category_id: i64, ) -> DomainResult<()>; } #[async_trait] pub trait MealRepository: Send + Sync { async fn create_meal( &self, txn: &mut SqliteConnection, name: String, description: String, category_id: Option, ) -> DomainResult; async fn get_meal( &self, txn: &mut SqliteConnection, meal_id: i64, ) -> DomainResult>; async fn list_meals(&self, txn: &mut SqliteConnection) -> DomainResult>; async fn update_meal( &self, txn: &mut SqliteConnection, meal_id: i64, name: String, description: String, category_id: Option, ) -> DomainResult<()>; async fn delete_meal(&self, txn: &mut SqliteConnection, meal_id: i64) -> DomainResult<()>; } #[async_trait] pub trait MealIngredientRepository: Send + Sync { async fn ingredients_for_meal( &self, txn: &mut SqliteConnection, meal_id: i64, ) -> DomainResult>; async fn add_ingredient( &self, txn: &mut SqliteConnection, meal_id: i64, name: String, quantity: String, note: String, category_id: Option, ) -> DomainResult; async fn update_ingredient( &self, txn: &mut SqliteConnection, meal_id: i64, ingredient_id: i64, name: String, quantity: String, note: String, category_id: Option, ) -> DomainResult<()>; async fn delete_ingredient( &self, txn: &mut SqliteConnection, meal_id: i64, ingredient_id: i64, ) -> DomainResult<()>; } #[async_trait] pub trait PasswordHasher: Send + Sync { fn hash(&self, password: &str) -> DomainResult; fn verify(&self, password: &str, encoded_hash: &str) -> DomainResult; } #[async_trait] pub trait TokenGenerator: Send + Sync { fn generate(&self) -> String; } #[async_trait] pub trait RealtimeNotifier: Send + Sync { async fn join(&self, list_id: i64, user_id: i64, display_name: String) -> Subscription; async fn leave(&self, list_id: i64, connection_id: &str); async fn publish_list_changed(&self, list_id: i64, revision: i64); async fn presence(&self, list_id: i64) -> Vec; } pub struct Subscription { pub connection_id: String, pub receiver: tokio::sync::broadcast::Receiver, pub presence: Vec, } #[derive(Clone, Debug)] pub enum HubEvent { ListChanged { list_id: i64, revision: i64 }, PresenceChanged { list_id: i64 }, } /// Stores and retrieves a user's rewards cards. #[async_trait] pub trait RewardsCardRepository: Send + Sync { async fn list_cards( &self, txn: &mut SqliteConnection, user_id: i64, ) -> DomainResult>; async fn create_card( &self, txn: &mut SqliteConnection, user_id: i64, store_name: String, number: String, symbology: String, ) -> DomainResult; async fn delete_card( &self, txn: &mut SqliteConnection, user_id: i64, card_id: i64, ) -> DomainResult<()>; }