feat: v2 rewrite — hexagonal arch, ActivityPub federation, NATS, deployment-ready #1

Merged
GKaszewski merged 334 commits from v2 into master 2026-05-16 09:42:43 +00:00
Showing only changes of commit 1ddb6a3954 - Show all commits

View File

@@ -1394,11 +1394,15 @@ impl domain::ports::FederationActionPort for ActivityPubService {
public_key: actor.public_key_pem.clone(),
avatar_url: actor.avatar_url.as_ref().map(|u| u.to_string()),
last_fetched_at: actor.last_refreshed_at,
bio: None,
banner_url: None,
also_known_as: None,
outbox_url: None,
attachment: vec![],
bio: actor.bio.clone(),
banner_url: actor.banner_url.as_ref().map(|u| u.to_string()),
also_known_as: actor.also_known_as.clone(),
outbox_url: Some(actor.outbox_url.to_string()),
attachment: actor
.attachment
.iter()
.map(|f| (f.name.clone(), f.value.clone()))
.collect(),
})
}
@@ -1519,12 +1523,61 @@ impl domain::ports::FederationActionPort for ActivityPubService {
async fn fetch_outbox_page(
&self,
_outbox_url: &str,
_page: u32,
outbox_url: &str,
page: u32,
) -> Result<Vec<domain::models::remote_note::RemoteNote>, domain::errors::DomainError> {
Err(domain::errors::DomainError::Internal(
"not implemented".into(),
))
use chrono::DateTime;
let url = format!("{}?page={}", outbox_url, page);
let resp: serde_json::Value = reqwest::Client::new()
.get(&url)
.header("Accept", "application/activity+json, application/ld+json")
.send()
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?
.json()
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?;
let empty = vec![];
let items = resp["orderedItems"].as_array().unwrap_or(&empty);
let notes = items
.iter()
.filter_map(|item| {
// Items are Create activities wrapping a Note, or Notes directly
let note = if item["type"].as_str() == Some("Create") {
&item["object"]
} else if item["type"].as_str() == Some("Note") {
item
} else {
return None;
};
// Only public notes
let to = note["to"].as_array()?;
let is_public = to
.iter()
.any(|t| t.as_str() == Some("https://www.w3.org/ns/activitystreams#Public"));
if !is_public {
return None;
}
let published = DateTime::parse_from_rfc3339(note["published"].as_str()?)
.ok()?
.with_timezone(&chrono::Utc);
Some(domain::models::remote_note::RemoteNote {
ap_id: note["id"].as_str()?.to_string(),
content: note["content"].as_str().unwrap_or("").to_string(),
published,
sensitive: note["sensitive"].as_bool().unwrap_or(false),
content_warning: note["summary"].as_str().map(|s| s.to_string()),
})
})
.collect();
Ok(notes)
}
}