feat: v2 rewrite — hexagonal arch, ActivityPub federation, NATS, deployment-ready (#1)
This commit was merged in pull request #1.
This commit is contained in:
7
crates/domain/src/models/actor_connection_summary.rs
Normal file
7
crates/domain/src/models/actor_connection_summary.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ActorConnectionSummary {
|
||||
pub url: String,
|
||||
pub handle: String,
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
}
|
||||
11
crates/domain/src/models/api_key.rs
Normal file
11
crates/domain/src/models/api_key.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::value_objects::{ApiKeyId, UserId};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiKey {
|
||||
pub id: ApiKeyId,
|
||||
pub user_id: UserId,
|
||||
pub key_hash: String,
|
||||
pub name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
14
crates/domain/src/models/connection_type.rs
Normal file
14
crates/domain/src/models/connection_type.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConnectionType {
|
||||
Followers,
|
||||
Following,
|
||||
}
|
||||
|
||||
impl ConnectionType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Followers => "followers",
|
||||
Self::Following => "following",
|
||||
}
|
||||
}
|
||||
}
|
||||
60
crates/domain/src/models/feed.rs
Normal file
60
crates/domain/src/models/feed.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::models::{thought::Thought, user::User};
|
||||
use crate::value_objects::UserId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngagementStats {
|
||||
pub like_count: i64,
|
||||
pub boost_count: i64,
|
||||
pub reply_count: i64,
|
||||
}
|
||||
|
||||
/// Present only when an authenticated viewer made the request.
|
||||
/// `liked`/`boosted` are the viewer's interaction state with this thought.
|
||||
/// `None` means anonymous request or viewer context unavailable.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ViewerContext {
|
||||
pub liked: bool,
|
||||
pub boosted: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FeedEntry {
|
||||
pub thought: Thought,
|
||||
pub author: User,
|
||||
pub stats: EngagementStats,
|
||||
pub viewer: Option<ViewerContext>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserSummary {
|
||||
pub id: UserId,
|
||||
pub username: String,
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub thought_count: i64,
|
||||
pub follower_count: i64,
|
||||
pub following_count: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PageParams {
|
||||
pub page: u64,
|
||||
pub per_page: u64,
|
||||
}
|
||||
impl PageParams {
|
||||
pub fn offset(&self) -> i64 {
|
||||
((self.page.saturating_sub(1)) * self.per_page) as i64
|
||||
}
|
||||
pub fn limit(&self) -> i64 {
|
||||
self.per_page as i64
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Paginated<T> {
|
||||
pub items: Vec<T>,
|
||||
pub total: i64,
|
||||
pub page: u64,
|
||||
pub per_page: u64,
|
||||
}
|
||||
12
crates/domain/src/models/mod.rs
Normal file
12
crates/domain/src/models/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod actor_connection_summary;
|
||||
pub mod api_key;
|
||||
pub mod connection_type;
|
||||
pub mod feed;
|
||||
pub mod notification;
|
||||
pub mod remote_actor;
|
||||
pub mod remote_note;
|
||||
pub mod social;
|
||||
pub mod tag;
|
||||
pub mod thought;
|
||||
pub mod top_friend;
|
||||
pub mod user;
|
||||
66
crates/domain/src/models/notification.rs
Normal file
66
crates/domain/src/models/notification.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::value_objects::{NotificationId, ThoughtId, UserId};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NotificationKind {
|
||||
Like {
|
||||
thought_id: ThoughtId,
|
||||
from_user_id: UserId,
|
||||
},
|
||||
Boost {
|
||||
thought_id: ThoughtId,
|
||||
from_user_id: UserId,
|
||||
},
|
||||
Reply {
|
||||
thought_id: ThoughtId,
|
||||
from_user_id: UserId,
|
||||
},
|
||||
Mention {
|
||||
thought_id: ThoughtId,
|
||||
from_user_id: UserId,
|
||||
},
|
||||
Follow {
|
||||
from_user_id: UserId,
|
||||
},
|
||||
}
|
||||
|
||||
impl NotificationKind {
|
||||
pub fn from_user_id(&self) -> &UserId {
|
||||
match self {
|
||||
Self::Like { from_user_id, .. } => from_user_id,
|
||||
Self::Boost { from_user_id, .. } => from_user_id,
|
||||
Self::Reply { from_user_id, .. } => from_user_id,
|
||||
Self::Mention { from_user_id, .. } => from_user_id,
|
||||
Self::Follow { from_user_id } => from_user_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn thought_id(&self) -> Option<&ThoughtId> {
|
||||
match self {
|
||||
Self::Like { thought_id, .. } => Some(thought_id),
|
||||
Self::Boost { thought_id, .. } => Some(thought_id),
|
||||
Self::Reply { thought_id, .. } => Some(thought_id),
|
||||
Self::Mention { thought_id, .. } => Some(thought_id),
|
||||
Self::Follow { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Like { .. } => "like",
|
||||
Self::Boost { .. } => "boost",
|
||||
Self::Reply { .. } => "reply",
|
||||
Self::Mention { .. } => "mention",
|
||||
Self::Follow { .. } => "follow",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Notification {
|
||||
pub id: NotificationId,
|
||||
pub user_id: UserId,
|
||||
pub kind: NotificationKind,
|
||||
pub read: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
17
crates/domain/src/models/remote_actor.rs
Normal file
17
crates/domain/src/models/remote_actor.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteActor {
|
||||
pub url: String,
|
||||
pub handle: String,
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub banner_url: Option<String>,
|
||||
pub also_known_as: Option<String>,
|
||||
pub outbox_url: Option<String>,
|
||||
pub followers_url: Option<String>,
|
||||
pub following_url: Option<String>,
|
||||
pub attachment: Vec<(String, String)>,
|
||||
pub last_fetched_at: DateTime<Utc>,
|
||||
}
|
||||
10
crates/domain/src/models/remote_note.rs
Normal file
10
crates/domain/src/models/remote_note.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteNote {
|
||||
pub ap_id: String,
|
||||
pub content: String,
|
||||
pub published: DateTime<Utc>,
|
||||
pub sensitive: bool,
|
||||
pub content_warning: Option<String>,
|
||||
}
|
||||
64
crates/domain/src/models/social.rs
Normal file
64
crates/domain/src/models/social.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use crate::value_objects::{BoostId, LikeId, ThoughtId, UserId};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Like {
|
||||
pub id: LikeId,
|
||||
pub user_id: UserId,
|
||||
pub thought_id: ThoughtId,
|
||||
pub ap_id: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Boost {
|
||||
pub id: BoostId,
|
||||
pub user_id: UserId,
|
||||
pub thought_id: ThoughtId,
|
||||
pub ap_id: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FollowState {
|
||||
Pending,
|
||||
Accepted,
|
||||
Rejected,
|
||||
}
|
||||
|
||||
impl FollowState {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Pending => "pending",
|
||||
Self::Accepted => "accepted",
|
||||
Self::Rejected => "rejected",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_db_str(s: &str) -> Result<Self, crate::errors::DomainError> {
|
||||
match s {
|
||||
"pending" => Ok(Self::Pending),
|
||||
"accepted" => Ok(Self::Accepted),
|
||||
"rejected" => Ok(Self::Rejected),
|
||||
other => Err(crate::errors::DomainError::Internal(format!(
|
||||
"unknown follow_state: '{other}'"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Follow {
|
||||
pub follower_id: UserId,
|
||||
pub following_id: UserId,
|
||||
pub state: FollowState,
|
||||
pub ap_id: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Block {
|
||||
pub blocker_id: UserId,
|
||||
pub blocked_id: UserId,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
5
crates/domain/src/models/tag.rs
Normal file
5
crates/domain/src/models/tag.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tag {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
72
crates/domain/src/models/thought.rs
Normal file
72
crates/domain/src/models/thought.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use crate::value_objects::{Content, ThoughtId, UserId};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Visibility {
|
||||
Public,
|
||||
Followers,
|
||||
Unlisted,
|
||||
Direct,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Thought {
|
||||
pub id: ThoughtId,
|
||||
pub user_id: UserId,
|
||||
pub content: Content,
|
||||
pub in_reply_to_id: Option<ThoughtId>,
|
||||
pub visibility: Visibility,
|
||||
pub content_warning: Option<String>,
|
||||
pub sensitive: bool,
|
||||
pub local: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Visibility {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Visibility::Public => "public",
|
||||
Visibility::Followers => "followers",
|
||||
Visibility::Unlisted => "unlisted",
|
||||
Visibility::Direct => "direct",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_db_str(s: &str) -> Result<Self, crate::errors::DomainError> {
|
||||
match s {
|
||||
"public" => Ok(Self::Public),
|
||||
"followers" => Ok(Self::Followers),
|
||||
"unlisted" => Ok(Self::Unlisted),
|
||||
"direct" => Ok(Self::Direct),
|
||||
other => Err(crate::errors::DomainError::Internal(format!(
|
||||
"unknown visibility: '{other}'"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Thought {
|
||||
pub fn new_local(
|
||||
id: ThoughtId,
|
||||
user_id: UserId,
|
||||
content: Content,
|
||||
in_reply_to_id: Option<ThoughtId>,
|
||||
visibility: Visibility,
|
||||
content_warning: Option<String>,
|
||||
sensitive: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
user_id,
|
||||
content,
|
||||
in_reply_to_id,
|
||||
visibility,
|
||||
content_warning,
|
||||
sensitive,
|
||||
local: true,
|
||||
created_at: Utc::now(),
|
||||
updated_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
8
crates/domain/src/models/top_friend.rs
Normal file
8
crates/domain/src/models/top_friend.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use crate::value_objects::UserId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TopFriend {
|
||||
pub user_id: UserId,
|
||||
pub friend_id: UserId,
|
||||
pub position: i16,
|
||||
}
|
||||
43
crates/domain/src/models/user.rs
Normal file
43
crates/domain/src/models/user.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::value_objects::{Email, PasswordHash, UserId, Username};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct User {
|
||||
pub id: UserId,
|
||||
pub username: Username,
|
||||
pub email: Email,
|
||||
pub password_hash: PasswordHash,
|
||||
pub display_name: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub header_url: Option<String>,
|
||||
pub custom_css: Option<String>,
|
||||
pub local: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new_local(
|
||||
id: UserId,
|
||||
username: Username,
|
||||
email: Email,
|
||||
password_hash: PasswordHash,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
password_hash,
|
||||
display_name: None,
|
||||
bio: None,
|
||||
avatar_url: None,
|
||||
header_url: None,
|
||||
custom_css: None,
|
||||
local: true,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user