use std::sync::Arc; use anyhow::{anyhow, Result}; use async_trait::async_trait; use chrono::{DateTime, Utc}; use url::Url; use activitypub_base::ApObjectHandler; use domain::ports::ActivityPubRepository; use domain::value_objects::UserId; use crate::note::ThoughtNote; use crate::urls::ThoughtsUrls; pub struct ThoughtsObjectHandler { repo: Arc, urls: ThoughtsUrls, } impl ThoughtsObjectHandler { pub fn new(repo: Arc, base_url: &str) -> Self { Self { repo, urls: ThoughtsUrls::new(base_url) } } } #[async_trait] impl ApObjectHandler for ThoughtsObjectHandler { async fn get_local_objects_for_user( &self, user_id: uuid::Uuid, ) -> Result> { let uid = UserId::from_uuid(user_id); let entries = self.repo.outbox_entries_for_actor(&uid).await .map_err(|e| anyhow!("{e}"))?; entries.into_iter().map(|e| { let note_url = self.urls.thought_url(e.thought.id.as_uuid()); let actor_url = self.urls.user_url(e.author_username.as_str()); let followers = self.urls.user_followers(e.author_username.as_str()); let in_reply_to = e.thought.in_reply_to_id.map(|id| self.urls.thought_url(id.as_uuid())); let note = ThoughtNote::new_public( note_url.clone(), actor_url, e.thought.content.as_str().to_owned(), e.thought.created_at, in_reply_to, e.thought.sensitive, e.thought.content_warning, followers, ); Ok((note_url, serde_json::to_value(¬e)?)) }).collect() } async fn get_local_objects_page( &self, user_id: uuid::Uuid, before: Option>, limit: usize, ) -> Result)>> { let uid = UserId::from_uuid(user_id); let entries = self.repo.outbox_page_for_actor(&uid, before, limit).await .map_err(|e| anyhow!("{e}"))?; entries.into_iter().map(|e| { let created_at = e.thought.created_at; let note_url = self.urls.thought_url(e.thought.id.as_uuid()); let actor_url = self.urls.user_url(e.author_username.as_str()); let followers = self.urls.user_followers(e.author_username.as_str()); let in_reply_to = e.thought.in_reply_to_id.map(|id| self.urls.thought_url(id.as_uuid())); let note = ThoughtNote::new_public( note_url.clone(), actor_url, e.thought.content.as_str().to_owned(), created_at, in_reply_to, e.thought.sensitive, e.thought.content_warning, followers, ); Ok((note_url, serde_json::to_value(¬e)?, created_at)) }).collect() } async fn on_create( &self, ap_id: &Url, actor_url: &Url, object: serde_json::Value, ) -> Result<()> { let note: ThoughtNote = serde_json::from_value(object)?; let author_id = self.repo.intern_remote_actor(actor_url).await .map_err(|e| anyhow!("{e}"))?; self.repo.accept_note( ap_id, &author_id, ¬e.content, note.published, note.sensitive, note.summary, ).await.map_err(|e| anyhow!("{e}")) } async fn on_update( &self, ap_id: &Url, _actor_url: &Url, object: serde_json::Value, ) -> Result<()> { let note: ThoughtNote = serde_json::from_value(object)?; self.repo.apply_note_update(ap_id, ¬e.content).await .map_err(|e| anyhow!("{e}")) } async fn on_delete(&self, ap_id: &Url, _actor_url: &Url) -> Result<()> { self.repo.retract_note(ap_id).await.map_err(|e| anyhow!("{e}")) } async fn on_actor_removed(&self, actor_url: &Url) -> Result<()> { self.repo.retract_actor_notes(actor_url).await.map_err(|e| anyhow!("{e}")) } async fn count_local_posts(&self) -> Result { self.repo.count_local_notes().await.map_err(|e| anyhow!("{e}")) } }