157 lines
7.3 KiB
Rust
157 lines
7.3 KiB
Rust
use async_trait::async_trait;
|
|
use crate::{
|
|
errors::DomainError,
|
|
events::{DomainEvent, EventEnvelope},
|
|
models::{
|
|
api_key::ApiKey,
|
|
feed::{FeedEntry, PageParams, Paginated, UserSummary},
|
|
notification::Notification,
|
|
remote_actor::RemoteActor,
|
|
social::{Block, Boost, Follow, FollowState, Like},
|
|
tag::Tag,
|
|
thought::Thought,
|
|
top_friend::TopFriend,
|
|
user::User,
|
|
},
|
|
value_objects::{ApiKeyId, Content, Email, NotificationId, PasswordHash, ThoughtId, UserId, Username},
|
|
};
|
|
|
|
pub struct GeneratedToken { pub token: String, pub user_id: UserId }
|
|
|
|
#[async_trait]
|
|
pub trait AuthService: Send + Sync {
|
|
fn generate_token(&self, user_id: &UserId) -> Result<GeneratedToken, DomainError>;
|
|
fn validate_token(&self, token: &str) -> Result<UserId, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait PasswordHasher: Send + Sync {
|
|
async fn hash(&self, plain: &str) -> Result<PasswordHash, DomainError>;
|
|
async fn verify(&self, plain: &str, hash: &PasswordHash) -> Result<bool, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait EventPublisher: Send + Sync {
|
|
async fn publish(&self, event: &DomainEvent) -> Result<(), DomainError>;
|
|
}
|
|
|
|
pub trait EventConsumer: Send + Sync {
|
|
fn consume(&self) -> futures::stream::BoxStream<'_, Result<EventEnvelope, DomainError>>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait UserRepository: Send + Sync {
|
|
async fn find_by_id(&self, id: &UserId) -> Result<Option<User>, DomainError>;
|
|
async fn find_by_username(&self, username: &Username) -> Result<Option<User>, DomainError>;
|
|
async fn find_by_email(&self, email: &Email) -> Result<Option<User>, DomainError>;
|
|
async fn save(&self, user: &User) -> Result<(), DomainError>;
|
|
async fn update_profile(&self, user_id: &UserId, display_name: Option<String>, bio: Option<String>, avatar_url: Option<String>, header_url: Option<String>, custom_css: Option<String>) -> Result<(), DomainError>;
|
|
async fn list_with_stats(&self) -> Result<Vec<UserSummary>, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ThoughtRepository: Send + Sync {
|
|
async fn save(&self, thought: &Thought) -> Result<(), DomainError>;
|
|
async fn find_by_id(&self, id: &ThoughtId) -> Result<Option<Thought>, DomainError>;
|
|
async fn delete(&self, id: &ThoughtId, user_id: &UserId) -> Result<(), DomainError>;
|
|
async fn update_content(&self, id: &ThoughtId, content: &Content) -> Result<(), DomainError>;
|
|
async fn get_thread(&self, id: &ThoughtId) -> Result<Vec<Thought>, DomainError>;
|
|
async fn list_by_user(&self, user_id: &UserId, page: &PageParams) -> Result<Paginated<FeedEntry>, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait LikeRepository: Send + Sync {
|
|
async fn save(&self, like: &Like) -> Result<(), DomainError>;
|
|
async fn delete(&self, user_id: &UserId, thought_id: &ThoughtId) -> Result<(), DomainError>;
|
|
async fn find(&self, user_id: &UserId, thought_id: &ThoughtId) -> Result<Option<Like>, DomainError>;
|
|
async fn count_for_thought(&self, thought_id: &ThoughtId) -> Result<i64, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait BoostRepository: Send + Sync {
|
|
async fn save(&self, boost: &Boost) -> Result<(), DomainError>;
|
|
async fn delete(&self, user_id: &UserId, thought_id: &ThoughtId) -> Result<(), DomainError>;
|
|
async fn find(&self, user_id: &UserId, thought_id: &ThoughtId) -> Result<Option<Boost>, DomainError>;
|
|
async fn count_for_thought(&self, thought_id: &ThoughtId) -> Result<i64, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait FollowRepository: Send + Sync {
|
|
async fn save(&self, follow: &Follow) -> Result<(), DomainError>;
|
|
async fn delete(&self, follower_id: &UserId, following_id: &UserId) -> Result<(), DomainError>;
|
|
async fn find(&self, follower_id: &UserId, following_id: &UserId) -> Result<Option<Follow>, DomainError>;
|
|
async fn update_state(&self, follower_id: &UserId, following_id: &UserId, state: &FollowState) -> Result<(), DomainError>;
|
|
async fn list_followers(&self, user_id: &UserId, page: &PageParams) -> Result<Paginated<User>, DomainError>;
|
|
async fn list_following(&self, user_id: &UserId, page: &PageParams) -> Result<Paginated<User>, DomainError>;
|
|
async fn get_accepted_following_ids(&self, user_id: &UserId) -> Result<Vec<UserId>, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait BlockRepository: Send + Sync {
|
|
async fn save(&self, block: &Block) -> Result<(), DomainError>;
|
|
async fn delete(&self, blocker_id: &UserId, blocked_id: &UserId) -> Result<(), DomainError>;
|
|
async fn exists(&self, blocker_id: &UserId, blocked_id: &UserId) -> Result<bool, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait TagRepository: Send + Sync {
|
|
async fn find_or_create(&self, name: &str) -> Result<Tag, DomainError>;
|
|
async fn attach_to_thought(&self, thought_id: &ThoughtId, tag_id: i32) -> Result<(), DomainError>;
|
|
async fn detach_from_thought(&self, thought_id: &ThoughtId) -> Result<(), DomainError>;
|
|
async fn list_for_thought(&self, thought_id: &ThoughtId) -> Result<Vec<Tag>, DomainError>;
|
|
async fn list_thoughts_by_tag(&self, tag_name: &str, page: &PageParams) -> Result<Paginated<Thought>, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ApiKeyRepository: Send + Sync {
|
|
async fn save(&self, key: &ApiKey) -> Result<(), DomainError>;
|
|
async fn find_by_hash(&self, key_hash: &str) -> Result<Option<ApiKey>, DomainError>;
|
|
async fn list_for_user(&self, user_id: &UserId) -> Result<Vec<ApiKey>, DomainError>;
|
|
async fn delete(&self, id: &ApiKeyId, user_id: &UserId) -> Result<(), DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait TopFriendRepository: Send + Sync {
|
|
async fn set_top_friends(&self, user_id: &UserId, friends: Vec<(UserId, i16)>) -> Result<(), DomainError>;
|
|
async fn list_for_user(&self, user_id: &UserId) -> Result<Vec<(TopFriend, User)>, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait NotificationRepository: Send + Sync {
|
|
async fn save(&self, n: &Notification) -> Result<(), DomainError>;
|
|
async fn list_for_user(&self, user_id: &UserId, page: &PageParams) -> Result<Paginated<Notification>, DomainError>;
|
|
async fn mark_read(&self, id: &NotificationId, user_id: &UserId) -> Result<(), DomainError>;
|
|
async fn mark_all_read(&self, user_id: &UserId) -> Result<(), DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait RemoteActorRepository: Send + Sync {
|
|
async fn upsert(&self, actor: &RemoteActor) -> Result<(), DomainError>;
|
|
async fn find_by_url(&self, url: &str) -> Result<Option<RemoteActor>, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait FeedRepository: Send + Sync {
|
|
async fn home_feed(&self, following_ids: &[UserId], page: &PageParams, viewer_id: Option<&UserId>) -> Result<Paginated<FeedEntry>, DomainError>;
|
|
async fn public_feed(&self, page: &PageParams, viewer_id: Option<&UserId>) -> Result<Paginated<FeedEntry>, DomainError>;
|
|
async fn search(&self, query: &str, page: &PageParams, viewer_id: Option<&UserId>) -> Result<Paginated<FeedEntry>, DomainError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait SearchPort: Send + Sync {
|
|
/// Full-text search over public thoughts, ranked by trigram similarity.
|
|
async fn search_thoughts(
|
|
&self,
|
|
query: &str,
|
|
page: &PageParams,
|
|
viewer_id: Option<&UserId>,
|
|
) -> Result<Paginated<FeedEntry>, DomainError>;
|
|
|
|
/// Search users by username or display_name, ranked by trigram similarity.
|
|
async fn search_users(
|
|
&self,
|
|
query: &str,
|
|
page: &PageParams,
|
|
) -> Result<Paginated<User>, DomainError>;
|
|
}
|