fix: FollowTarget enum — actor_url no longer lies about holding a handle
FollowCommand.target is now FollowTarget (Identity|Handle) instead of SocialIdentity. actor_url field always holds a URL; handles go through FollowTarget::Handle. Adapter resolves handles explicitly. Type system prevents misuse in future commands.
This commit is contained in:
@@ -4,7 +4,7 @@ use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
ports::{SocialCommand, SocialQuery, UserRepository},
|
||||
value_objects::{SocialActor, SocialIdentity, UserId},
|
||||
value_objects::{FollowTarget, SocialActor, SocialIdentity, UserId},
|
||||
};
|
||||
|
||||
use k_ap::RemoteActor;
|
||||
@@ -87,15 +87,18 @@ fn ap_err(e: anyhow::Error) -> DomainError {
|
||||
|
||||
#[async_trait]
|
||||
impl SocialCommand for CompositeSocialAdapter {
|
||||
async fn follow(&self, follower: &UserId, target: &SocialIdentity) -> Result<(), DomainError> {
|
||||
if let SocialIdentity::Local(target_id) = target
|
||||
async fn follow(&self, follower: &UserId, target: &FollowTarget) -> Result<(), DomainError> {
|
||||
if let FollowTarget::Identity(SocialIdentity::Local(target_id)) = target
|
||||
&& follower == target_id
|
||||
{
|
||||
return Err(DomainError::ValidationError(
|
||||
"Cannot follow yourself".into(),
|
||||
));
|
||||
}
|
||||
let handle = self.resolve_handle(target).await?;
|
||||
let handle = match target {
|
||||
FollowTarget::Handle(h) => h.clone(),
|
||||
FollowTarget::Identity(id) => self.resolve_handle(id).await?,
|
||||
};
|
||||
self.ap_service
|
||||
.follow(follower.value(), &handle)
|
||||
.await
|
||||
|
||||
@@ -196,6 +196,13 @@ fn identity_to_payload(id: &SocialIdentity) -> (String, String) {
|
||||
}
|
||||
}
|
||||
|
||||
fn follow_target_to_payload(target: &domain::value_objects::FollowTarget) -> (String, String) {
|
||||
match target {
|
||||
domain::value_objects::FollowTarget::Identity(id) => identity_to_payload(id),
|
||||
domain::value_objects::FollowTarget::Handle(h) => ("handle".into(), h.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn payload_to_identity(kind: &str, id: String) -> Result<SocialIdentity, DomainError> {
|
||||
match kind {
|
||||
"local" => Ok(SocialIdentity::Local(UserId::from_uuid(parse_uuid(
|
||||
@@ -208,6 +215,18 @@ fn payload_to_identity(kind: &str, id: String) -> Result<SocialIdentity, DomainE
|
||||
}
|
||||
}
|
||||
|
||||
fn payload_to_follow_target(
|
||||
kind: &str,
|
||||
id: String,
|
||||
) -> Result<domain::value_objects::FollowTarget, DomainError> {
|
||||
match kind {
|
||||
"handle" => Ok(domain::value_objects::FollowTarget::Handle(id)),
|
||||
other => Ok(domain::value_objects::FollowTarget::Identity(
|
||||
payload_to_identity(other, id)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ts(ts: i64) -> Result<NaiveDateTime, DomainError> {
|
||||
chrono::DateTime::from_timestamp(ts, 0)
|
||||
.map(|dt| dt.naive_utc())
|
||||
@@ -294,7 +313,7 @@ impl From<&DomainEvent> for EventPayload {
|
||||
}
|
||||
}
|
||||
DomainEvent::FollowRequested { follower, target } => {
|
||||
let (kind, id) = identity_to_payload(target);
|
||||
let (kind, id) = follow_target_to_payload(target);
|
||||
EventPayload::FollowRequested {
|
||||
follower_id: follower.value().to_string(),
|
||||
target_kind: kind,
|
||||
@@ -530,7 +549,7 @@ impl TryFrom<EventPayload> for DomainEvent {
|
||||
target_id,
|
||||
} => Ok(DomainEvent::FollowRequested {
|
||||
follower: UserId::from_uuid(parse_uuid(&follower_id, "follower_id")?),
|
||||
target: payload_to_identity(&target_kind, target_id)?,
|
||||
target: payload_to_follow_target(&target_kind, target_id)?,
|
||||
}),
|
||||
EventPayload::FollowAccepted {
|
||||
owner_id,
|
||||
|
||||
Reference in New Issue
Block a user