341 lines
9.3 KiB
Rust
341 lines
9.3 KiB
Rust
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<User>;
|
|
async fn find_user_by_email(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
email: String,
|
|
) -> DomainResult<Option<(User, String)>>;
|
|
async fn find_user_by_handle(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
user_handle: Vec<u8>,
|
|
) -> DomainResult<Option<User>>;
|
|
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<bool>;
|
|
}
|
|
|
|
#[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<Passkey>;
|
|
async fn find_by_credential_id(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
credential_id: String,
|
|
) -> DomainResult<Option<Passkey>>;
|
|
async fn list_for_user(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
user_id: i64,
|
|
) -> DomainResult<Vec<Passkey>>;
|
|
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<Option<SessionUser>>;
|
|
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<Vec<GroceryList>>;
|
|
async fn list_archived_summaries(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
) -> DomainResult<Vec<GroceryList>>;
|
|
async fn create_list(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
name: String,
|
|
) -> DomainResult<GroceryList>;
|
|
async fn get_list(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
) -> DomainResult<Option<GroceryList>>;
|
|
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<Vec<Category>>;
|
|
async fn create_category(&self, txn: &mut SqliteConnection, name: String) -> DomainResult<i64>;
|
|
}
|
|
|
|
/// 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<i64>,
|
|
/// 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<i64>,
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ItemRepository: Send + Sync {
|
|
async fn items(&self, txn: &mut SqliteConnection, list_id: i64) -> DomainResult<Vec<Item>>;
|
|
async fn add_item(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
name: String,
|
|
quantity: String,
|
|
note: String,
|
|
category_id: Option<i64>,
|
|
) -> DomainResult<i64>;
|
|
async fn add_items_bulk(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
items: Vec<NewItem>,
|
|
) -> DomainResult<i64>;
|
|
async fn set_item_checked(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
item_id: i64,
|
|
checked: bool,
|
|
) -> DomainResult<i64>;
|
|
async fn update_item(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
item_id: i64,
|
|
name: String,
|
|
quantity: String,
|
|
note: String,
|
|
category_id: Option<i64>,
|
|
) -> DomainResult<i64>;
|
|
async fn delete_item(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
item_id: i64,
|
|
) -> DomainResult<i64>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ListMealRepository: Send + Sync {
|
|
async fn list_meals(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
) -> DomainResult<Vec<ListMeal>>;
|
|
async fn add_meal(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
meal_id: i64,
|
|
name: String,
|
|
) -> DomainResult<i64>;
|
|
async fn remove_meal(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
list_id: i64,
|
|
list_meal_id: i64,
|
|
) -> DomainResult<i64>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait InvitationRepository: Send + Sync {
|
|
async fn create_invitation(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
created_by: i64,
|
|
token: String,
|
|
) -> DomainResult<i64>;
|
|
async fn invitation(&self, txn: &mut SqliteConnection, token: String) -> DomainResult<bool>;
|
|
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<Vec<MealCategory>>;
|
|
async fn create_meal_category(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
name: String,
|
|
) -> DomainResult<i64>;
|
|
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<i64>,
|
|
) -> DomainResult<Meal>;
|
|
async fn get_meal(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
meal_id: i64,
|
|
) -> DomainResult<Option<Meal>>;
|
|
async fn list_meals(&self, txn: &mut SqliteConnection) -> DomainResult<Vec<Meal>>;
|
|
async fn update_meal(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
meal_id: i64,
|
|
name: String,
|
|
description: String,
|
|
category_id: Option<i64>,
|
|
) -> 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<Vec<MealIngredient>>;
|
|
async fn add_ingredient(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
meal_id: i64,
|
|
name: String,
|
|
quantity: String,
|
|
note: String,
|
|
category_id: Option<i64>,
|
|
) -> DomainResult<i64>;
|
|
async fn update_ingredient(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
meal_id: i64,
|
|
ingredient_id: i64,
|
|
name: String,
|
|
quantity: String,
|
|
note: String,
|
|
category_id: Option<i64>,
|
|
) -> 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<String>;
|
|
fn verify(&self, password: &str, encoded_hash: &str) -> DomainResult<bool>;
|
|
}
|
|
|
|
#[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<PresenceUser>;
|
|
}
|
|
|
|
pub struct Subscription {
|
|
pub connection_id: String,
|
|
pub receiver: tokio::sync::broadcast::Receiver<HubEvent>,
|
|
pub presence: Vec<PresenceUser>,
|
|
}
|
|
|
|
#[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<Vec<RewardsCard>>;
|
|
async fn create_card(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
user_id: i64,
|
|
store_name: String,
|
|
number: String,
|
|
symbology: String,
|
|
) -> DomainResult<RewardsCard>;
|
|
async fn delete_card(
|
|
&self,
|
|
txn: &mut SqliteConnection,
|
|
user_id: i64,
|
|
card_id: i64,
|
|
) -> DomainResult<()>;
|
|
}
|