feat: add federation processed activities table and update dependencies
- Created a new SQL migration to add the `federation_processed_activities` table with an index on `processed_at`. - Updated dependencies in `Cargo.toml` files across `bootstrap` and `worker` crates, including version updates for `k-ap`. - Enhanced the event publishing mechanism in the `factory.rs` file to include a new `KapPublisher` for handling federation events. - Refactored the `build` function in `factory.rs` to accommodate the new event publisher and improve ActivityPub service initialization. - Modified the worker's main loop to handle new federation event types and improved error handling for event processing. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -4,18 +4,17 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
k-ap = { git = "https://git.gabrielkaszewski.dev/GKaszewski/k-ap.git", tag = "v0.1.10" }
|
||||
domain = { workspace = true }
|
||||
url = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
activitypub_federation = "0.7.0-beta.11"
|
||||
reqwest = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
k-ap = { version = "0.3.0", registry = "gitea" }
|
||||
domain = { workspace = true }
|
||||
url = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::port::{AcceptNoteInput, ActivityPubRepository};
|
||||
use crate::urls::ThoughtsUrls;
|
||||
use domain::ports::{EventPublisher, TagRepository};
|
||||
use domain::value_objects::UserId;
|
||||
use k_ap::ApObjectHandler;
|
||||
use k_ap::{ApContentReader, ApObjectHandler};
|
||||
|
||||
pub struct ThoughtsObjectHandler {
|
||||
repo: Arc<dyn ActivityPubRepository>,
|
||||
@@ -37,43 +37,10 @@ impl ThoughtsObjectHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ApObjectHandler for ThoughtsObjectHandler {
|
||||
async fn get_local_objects_for_user(
|
||||
&self,
|
||||
user_id: uuid::Uuid,
|
||||
) -> Result<Vec<(Url, serde_json::Value)>> {
|
||||
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(&user_id.to_string());
|
||||
let followers = self.urls.user_followers(&user_id.to_string());
|
||||
let in_reply_to = e
|
||||
.thought
|
||||
.in_reply_to_id
|
||||
.map(|id| self.urls.thought_url(id.as_uuid()));
|
||||
let note = ThoughtNote::new_public(ThoughtNoteInput {
|
||||
id: note_url.clone(),
|
||||
actor_url,
|
||||
content: e.thought.content.as_str().to_owned(),
|
||||
published: e.thought.created_at,
|
||||
in_reply_to,
|
||||
sensitive: e.thought.sensitive,
|
||||
summary: e.thought.content_warning,
|
||||
followers_url: followers,
|
||||
});
|
||||
Ok((note_url, serde_json::to_value(¬e)?))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
// ── ApContentReader ───────────────────────────────────────────────────────────
|
||||
|
||||
#[async_trait]
|
||||
impl ApContentReader for ThoughtsObjectHandler {
|
||||
async fn get_local_objects_page(
|
||||
&self,
|
||||
user_id: uuid::Uuid,
|
||||
@@ -112,6 +79,18 @@ impl ApObjectHandler for ThoughtsObjectHandler {
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn count_local_posts(&self) -> Result<u64> {
|
||||
self.repo
|
||||
.count_local_notes()
|
||||
.await
|
||||
.map_err(|e| anyhow!("{e}"))
|
||||
}
|
||||
}
|
||||
|
||||
// ── ApObjectHandler ───────────────────────────────────────────────────────────
|
||||
|
||||
#[async_trait]
|
||||
impl ApObjectHandler for ThoughtsObjectHandler {
|
||||
async fn on_create(
|
||||
&self,
|
||||
ap_id: &Url,
|
||||
@@ -128,7 +107,6 @@ impl ApObjectHandler for ThoughtsObjectHandler {
|
||||
.await
|
||||
.map_err(|e| anyhow!("{e}"))?;
|
||||
|
||||
// Derive visibility from AP addressing conventions.
|
||||
let as_public = "https://www.w3.org/ns/activitystreams#Public";
|
||||
let in_to = note.to.iter().any(|s| s == as_public);
|
||||
let in_cc = note.cc.iter().any(|s| s == as_public);
|
||||
@@ -161,7 +139,6 @@ impl ApObjectHandler for ThoughtsObjectHandler {
|
||||
.await
|
||||
.map_err(|e| anyhow!("{e}"))?;
|
||||
|
||||
// Extract and index hashtags from the AP tag array.
|
||||
let hashtag_names: Vec<String> = note
|
||||
.tag
|
||||
.iter()
|
||||
@@ -177,7 +154,6 @@ impl ApObjectHandler for ThoughtsObjectHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Fire mention notifications for local @mentions in the note's tag array.
|
||||
let base_url = url::Url::parse(&self.urls.base_url)
|
||||
.ok()
|
||||
.and_then(|u| u.host_str().map(|h| h.to_string()))
|
||||
@@ -408,10 +384,7 @@ impl ApObjectHandler for ThoughtsObjectHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn count_local_posts(&self) -> Result<u64> {
|
||||
self.repo
|
||||
.count_local_notes()
|
||||
.await
|
||||
.map_err(|e| anyhow!("{e}"))
|
||||
async fn on_announce_of_remote(&self, _object_url: &Url, _actor_url: &Url) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,17 @@ fn build_note_json(
|
||||
note
|
||||
}
|
||||
|
||||
fn thought_to_ap_visibility(
|
||||
v: &domain::models::thought::Visibility,
|
||||
) -> k_ap::ApVisibility {
|
||||
match v {
|
||||
domain::models::thought::Visibility::Public => k_ap::ApVisibility::Public,
|
||||
domain::models::thought::Visibility::Unlisted => k_ap::ApVisibility::Public,
|
||||
domain::models::thought::Visibility::Followers => k_ap::ApVisibility::FollowersOnly,
|
||||
domain::models::thought::Visibility::Direct => k_ap::ApVisibility::Private,
|
||||
}
|
||||
}
|
||||
|
||||
fn k_ap_actor_to_domain(a: k_ap::RemoteActor) -> DomainRemoteActor {
|
||||
DomainRemoteActor {
|
||||
url: a.url,
|
||||
@@ -264,7 +275,12 @@ impl crate::port::OutboundFederationPort for ApFederationAdapter {
|
||||
in_reply_to_url,
|
||||
);
|
||||
self.inner
|
||||
.broadcast_create_note(user_uuid, note)
|
||||
.broadcast_create_note(
|
||||
user_uuid,
|
||||
note,
|
||||
thought_to_ap_visibility(&thought.visibility),
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| DomainError::Internal(e.to_string()))
|
||||
}
|
||||
@@ -300,7 +316,12 @@ impl crate::port::OutboundFederationPort for ApFederationAdapter {
|
||||
in_reply_to_url,
|
||||
);
|
||||
self.inner
|
||||
.broadcast_update_note(user_uuid, note)
|
||||
.broadcast_update_note(
|
||||
user_uuid,
|
||||
note,
|
||||
thought_to_ap_visibility(&thought.visibility),
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| DomainError::Internal(e.to_string()))
|
||||
}
|
||||
@@ -384,7 +405,7 @@ impl FederationSchedulerPort for ApFederationAdapter {
|
||||
let actor = actor_ap_url.to_string();
|
||||
let outbox = outbox_url.to_string();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = service.backfill_outbox(&outbox, &actor).await {
|
||||
if let Err(e) = service.import_remote_outbox(&outbox, &actor).await {
|
||||
tracing::warn!(actor = %actor, error = %e, "posts backfill failed");
|
||||
}
|
||||
});
|
||||
@@ -517,7 +538,7 @@ impl FederationLookupPort for ApFederationAdapter {
|
||||
last_fetched_at: chrono::Utc::now(),
|
||||
bio: actor.bio,
|
||||
banner_url: actor.banner_url.as_ref().map(|u| u.to_string()),
|
||||
also_known_as: actor.also_known_as,
|
||||
also_known_as: actor.also_known_as.into_iter().next(),
|
||||
followers_url: actor.followers_url.as_ref().map(|u| u.to_string()),
|
||||
following_url: actor.following_url.as_ref().map(|u| u.to_string()),
|
||||
attachment: actor
|
||||
|
||||
Reference in New Issue
Block a user