fix(ap): visibility-aware addressing — correct to/cc outbound, parse inbound to/cc
Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 9m25s
test / unit (pull_request) Successful in 16m39s
test / integration (pull_request) Failing after 17m35s
Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 9m25s
test / unit (pull_request) Successful in 16m39s
test / integration (pull_request) Failing after 17m35s
This commit is contained in:
@@ -36,6 +36,23 @@ fn thought_note_json(
|
|||||||
base_url: &str,
|
base_url: &str,
|
||||||
) -> anyhow::Result<(url::Url, serde_json::Value)> {
|
) -> anyhow::Result<(url::Url, serde_json::Value)> {
|
||||||
let ap_id = url::Url::parse(&format!("{}/thoughts/{}", base_url, thought.id))?;
|
let ap_id = url::Url::parse(&format!("{}/thoughts/{}", base_url, thought.id))?;
|
||||||
|
|
||||||
|
// Build to/cc based on visibility per AP spec.
|
||||||
|
let (to, cc) = match thought.visibility {
|
||||||
|
domain::models::thought::Visibility::Public => (
|
||||||
|
vec![crate::urls::AS_PUBLIC.to_string()],
|
||||||
|
vec![local_actor.followers_url.to_string()],
|
||||||
|
),
|
||||||
|
domain::models::thought::Visibility::Unlisted => (
|
||||||
|
vec![local_actor.followers_url.to_string()],
|
||||||
|
vec![crate::urls::AS_PUBLIC.to_string()],
|
||||||
|
),
|
||||||
|
domain::models::thought::Visibility::Followers => {
|
||||||
|
(vec![local_actor.followers_url.to_string()], vec![])
|
||||||
|
}
|
||||||
|
domain::models::thought::Visibility::Direct => (vec![], vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
let mut note = serde_json::json!({
|
let mut note = serde_json::json!({
|
||||||
"type": "Note",
|
"type": "Note",
|
||||||
"id": ap_id.to_string(),
|
"id": ap_id.to_string(),
|
||||||
@@ -43,8 +60,8 @@ fn thought_note_json(
|
|||||||
"attributedTo": local_actor.ap_id.to_string(),
|
"attributedTo": local_actor.ap_id.to_string(),
|
||||||
"content": thought.content.as_str(),
|
"content": thought.content.as_str(),
|
||||||
"published": thought.created_at.to_rfc3339(),
|
"published": thought.created_at.to_rfc3339(),
|
||||||
"to": [crate::urls::AS_PUBLIC],
|
"to": to,
|
||||||
"cc": [local_actor.followers_url.to_string()],
|
"cc": cc,
|
||||||
"sensitive": thought.sensitive,
|
"sensitive": thought.sensitive,
|
||||||
});
|
});
|
||||||
if let Some(ref cw) = thought.content_warning {
|
if let Some(ref cw) = thought.content_warning {
|
||||||
|
|||||||
@@ -111,6 +111,24 @@ impl ApObjectHandler for ThoughtsObjectHandler {
|
|||||||
.intern_remote_actor(actor_url)
|
.intern_remote_actor(actor_url)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow!("{e}"))?;
|
.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);
|
||||||
|
let has_followers = note.to.iter().any(|s| s.ends_with("/followers"))
|
||||||
|
|| note.cc.iter().any(|s| s.ends_with("/followers"));
|
||||||
|
|
||||||
|
let visibility = if in_to {
|
||||||
|
"public"
|
||||||
|
} else if in_cc {
|
||||||
|
"unlisted"
|
||||||
|
} else if has_followers {
|
||||||
|
"followers"
|
||||||
|
} else {
|
||||||
|
"direct"
|
||||||
|
};
|
||||||
|
|
||||||
self.repo
|
self.repo
|
||||||
.accept_note(
|
.accept_note(
|
||||||
ap_id,
|
ap_id,
|
||||||
@@ -119,6 +137,7 @@ impl ApObjectHandler for ThoughtsObjectHandler {
|
|||||||
note.published,
|
note.published,
|
||||||
note.sensitive,
|
note.sensitive,
|
||||||
note.summary,
|
note.summary,
|
||||||
|
visibility,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow!("{e}"))
|
.map_err(|e| anyhow!("{e}"))
|
||||||
|
|||||||
@@ -187,11 +187,12 @@ impl ActivityPubRepository for PgActivityPubRepository {
|
|||||||
published: DateTime<Utc>,
|
published: DateTime<Utc>,
|
||||||
sensitive: bool,
|
sensitive: bool,
|
||||||
content_warning: Option<String>,
|
content_warning: Option<String>,
|
||||||
|
visibility: &str,
|
||||||
) -> Result<(), DomainError> {
|
) -> Result<(), DomainError> {
|
||||||
let capped: String = content.chars().take(500).collect();
|
let capped: String = content.chars().take(500).collect();
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"INSERT INTO thoughts(id,user_id,content,ap_id,visibility,sensitive,local,content_warning,created_at)
|
"INSERT INTO thoughts(id,user_id,content,ap_id,visibility,sensitive,local,content_warning,created_at)
|
||||||
VALUES($1,$2,$3,$4,'public',$5,false,$6,$7) ON CONFLICT(ap_id) DO NOTHING",
|
VALUES($1,$2,$3,$4,$8,$5,false,$6,$7) ON CONFLICT(ap_id) DO NOTHING",
|
||||||
)
|
)
|
||||||
.bind(uuid::Uuid::new_v4())
|
.bind(uuid::Uuid::new_v4())
|
||||||
.bind(author_id.as_uuid())
|
.bind(author_id.as_uuid())
|
||||||
@@ -200,6 +201,7 @@ impl ActivityPubRepository for PgActivityPubRepository {
|
|||||||
.bind(sensitive)
|
.bind(sensitive)
|
||||||
.bind(content_warning)
|
.bind(content_warning)
|
||||||
.bind(published)
|
.bind(published)
|
||||||
|
.bind(visibility)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DomainError::Internal(e.to_string()))
|
.map_err(|e| DomainError::Internal(e.to_string()))
|
||||||
@@ -275,6 +277,7 @@ mod tests {
|
|||||||
chrono::Utc::now(),
|
chrono::Utc::now(),
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
|
"public",
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ pub trait ActivityPubRepository: Send + Sync {
|
|||||||
// ── Inbox processing (remote → local) ───────────────────────────
|
// ── Inbox processing (remote → local) ───────────────────────────
|
||||||
|
|
||||||
/// Persist an incoming remote Note. Idempotent on ap_id.
|
/// Persist an incoming remote Note. Idempotent on ap_id.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn accept_note(
|
async fn accept_note(
|
||||||
&self,
|
&self,
|
||||||
ap_id: &url::Url,
|
ap_id: &url::Url,
|
||||||
@@ -296,6 +297,7 @@ pub trait ActivityPubRepository: Send + Sync {
|
|||||||
published: chrono::DateTime<chrono::Utc>,
|
published: chrono::DateTime<chrono::Utc>,
|
||||||
sensitive: bool,
|
sensitive: bool,
|
||||||
content_warning: Option<String>,
|
content_warning: Option<String>,
|
||||||
|
visibility: &str,
|
||||||
) -> Result<(), DomainError>;
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
/// Apply an Update to a previously accepted remote Note.
|
/// Apply an Update to a previously accepted remote Note.
|
||||||
|
|||||||
@@ -698,6 +698,7 @@ impl ActivityPubRepository for TestStore {
|
|||||||
_published: chrono::DateTime<chrono::Utc>,
|
_published: chrono::DateTime<chrono::Utc>,
|
||||||
_sensitive: bool,
|
_sensitive: bool,
|
||||||
_content_warning: Option<String>,
|
_content_warning: Option<String>,
|
||||||
|
_visibility: &str,
|
||||||
) -> Result<(), DomainError> {
|
) -> Result<(), DomainError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user