ADR-0003: local follows bypass AP, unified FollowCommand/FollowQuery ports, FederationRepos struct, base_url normalization
Some checks failed
CI / Check / Test (push) Has been cancelled

This commit is contained in:
2026-07-27 12:56:30 +02:00
parent 839cababe4
commit 063bab910b
16 changed files with 978 additions and 228 deletions

View File

@@ -0,0 +1,80 @@
use async_trait::async_trait;
use crate::{
errors::DomainError,
value_objects::{FollowStatus, SocialActor},
};
#[async_trait]
pub trait FollowCommand: Send + Sync {
async fn add_follow(
&self,
follower_id: uuid::Uuid,
target_actor_url: &str,
status: FollowStatus,
) -> Result<(), DomainError>;
async fn update_follow_status(
&self,
follower_id: uuid::Uuid,
target_actor_url: &str,
status: FollowStatus,
) -> Result<(), DomainError>;
async fn remove_follow(
&self,
follower_id: uuid::Uuid,
target_actor_url: &str,
) -> Result<(), DomainError>;
async fn add_follower(
&self,
local_user_id: uuid::Uuid,
follower_actor_url: &str,
status: FollowStatus,
) -> Result<(), DomainError>;
async fn update_follower_status(
&self,
local_user_id: uuid::Uuid,
follower_actor_url: &str,
status: FollowStatus,
) -> Result<(), DomainError>;
async fn remove_follower_record(
&self,
local_user_id: uuid::Uuid,
follower_actor_url: &str,
) -> Result<(), DomainError>;
}
#[async_trait]
pub trait FollowQuery: Send + Sync {
async fn get_following(
&self,
user_id: uuid::Uuid,
base_url: &str,
) -> Result<Vec<SocialActor>, DomainError>;
async fn get_followers(
&self,
user_id: uuid::Uuid,
base_url: &str,
) -> Result<Vec<SocialActor>, DomainError>;
async fn get_pending_followers(
&self,
user_id: uuid::Uuid,
base_url: &str,
) -> Result<Vec<SocialActor>, DomainError>;
async fn count_following(&self, user_id: uuid::Uuid) -> Result<usize, DomainError>;
async fn count_followers(&self, user_id: uuid::Uuid) -> Result<usize, DomainError>;
async fn is_following(
&self,
follower_id: uuid::Uuid,
target_actor_url: &str,
) -> Result<bool, DomainError>;
}

View File

@@ -2,6 +2,7 @@ pub mod auth;
pub mod diary;
pub mod events;
pub mod federated_profile;
pub mod follow;
pub mod goals;
pub mod image_fetcher;
pub mod images;
@@ -21,6 +22,7 @@ pub use auth::*;
pub use diary::*;
pub use events::*;
pub use federated_profile::*;
pub use follow::*;
pub use goals::*;
pub use image_fetcher::*;
pub use images::*;

View File

@@ -7,6 +7,18 @@ pub enum SocialIdentity {
}
impl SocialIdentity {
pub fn from_actor_url(actor_url: &str, base_url: &str) -> Self {
let prefix = format!("{}/users/", base_url);
if let Some(uuid_str) = actor_url.strip_prefix(&prefix)
&& let Ok(uuid) = uuid::Uuid::parse_str(uuid_str)
{
return Self::Local(UserId::from_uuid(uuid));
}
Self::Remote {
actor_url: actor_url.to_string(),
}
}
pub fn is_local(&self) -> bool {
matches!(self, Self::Local(_))
}
@@ -14,6 +26,26 @@ impl SocialIdentity {
pub fn is_remote(&self) -> bool {
matches!(self, Self::Remote { .. })
}
pub fn format_local_handle(username: &str, base_url: &str) -> String {
let host = Self::host_from_base_url(base_url);
format!("@{}@{}", username, host)
}
pub fn host_from_base_url(base_url: &str) -> &str {
base_url
.split("://")
.nth(1)
.and_then(|s| s.split('/').next())
.unwrap_or("localhost")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FollowStatus {
Pending,
Accepted,
Rejected,
}
#[derive(Clone, Debug, PartialEq, Eq)]