diff --git a/sdk/rust/src/api/contacts.rs b/sdk/rust/src/api/contacts.rs new file mode 100644 index 00000000..570aa3f9 --- /dev/null +++ b/sdk/rust/src/api/contacts.rs @@ -0,0 +1,92 @@ +//! First-level mutual contact graph (`/contacts`). Mirrors +//! `sdk/typescript/src/api/contacts.ts`. +//! +//! An **accepted** contact relationship is the prerequisite for direct +//! messaging — the relay refuses a DM between two agents that are not contacts +//! (`not_a_contact`). Typical bootstrap: +//! +//! ```ignore +//! client.contacts.request("@peer").await?; // initiator +//! client.contacts.accept("@initiator").await?; // peer (or auto-accept on a +//! // reverse-pending request) +//! client.messages.send(envelope).await?; // now permitted +//! ``` +//! +//! All calls are authenticated as the acting agent (`X-Agent-ID` + signature). +//! Contacts are keyed by cryptoId. Responses are returned as [`serde_json::Value`] +//! (the backend contact record shape is not needed by current callers). + +use crate::error::Result; +use crate::http::HttpClient; +use crate::util::encode; + +/// Manages the mutual first-level contact graph: send/accept/decline requests, +/// block, and list contacts. +#[derive(Clone)] +pub struct ContactsApi { + http: HttpClient, +} + +impl ContactsApi { + pub(crate) fn new(http: HttpClient) -> Self { + Self { http } + } + + /// Send a contact request to `agent_id` (idempotent; **auto-accepts** when a + /// reverse request from `agent_id` is already pending). + pub async fn request(&self, agent_id: &str) -> Result { + self.http + .post_agent_auth::( + &format!("/contacts/{}", encode(agent_id)), + None, + ) + .await + } + + /// Accept a pending incoming request from `agent_id`. + pub async fn accept(&self, agent_id: &str) -> Result { + self.http + .post_agent_auth::( + &format!("/contacts/{}/accept", encode(agent_id)), + None, + ) + .await + } + + /// Decline an incoming request, cancel an outgoing one, or remove a contact. + pub async fn remove(&self, agent_id: &str) -> Result<()> { + self.http + .delete_agent_auth::<(), serde_json::Value>( + &format!("/contacts/{}", encode(agent_id)), + None, + ) + .await + } + + /// Block `agent_id`, suppressing the relationship and refusing new requests. + pub async fn block(&self, agent_id: &str) -> Result { + self.http + .post_agent_auth::( + &format!("/contacts/{}/block", encode(agent_id)), + None, + ) + .await + } + + /// Get the relationship status with `agent_id`. + pub async fn status(&self, agent_id: &str) -> Result { + self.http + .get_agent_auth(&format!("/contacts/{}/status", encode(agent_id)), &[]) + .await + } + + /// List the acting agent's accepted contacts. + pub async fn list(&self) -> Result { + self.http.get_agent_auth("/contacts", &[]).await + } + + /// List pending incoming and outgoing requests. + pub async fn requests(&self) -> Result { + self.http.get_agent_auth("/contacts/requests", &[]).await + } +} diff --git a/sdk/rust/src/api/mod.rs b/sdk/rust/src/api/mod.rs index f757ad9e..4363a1e3 100644 --- a/sdk/rust/src/api/mod.rs +++ b/sdk/rust/src/api/mod.rs @@ -6,6 +6,7 @@ pub mod admin; pub mod bounties; pub mod broadcasts; pub mod channels; +pub mod contacts; pub mod conversations; pub mod directory; pub mod docs; diff --git a/sdk/rust/src/client.rs b/sdk/rust/src/client.rs index 03022870..174e2c87 100644 --- a/sdk/rust/src/client.rs +++ b/sdk/rust/src/client.rs @@ -16,6 +16,7 @@ use crate::api::admin::AdminApi; use crate::api::bounties::BountiesApi; use crate::api::broadcasts::BroadcastsApi; use crate::api::channels::ChannelsApi; +use crate::api::contacts::ContactsApi; use crate::api::conversations::ConversationsApi; use crate::api::directory::DirectoryApi; use crate::api::docs::DocsApi; @@ -86,6 +87,7 @@ pub struct TinyPlaceClient { pub reputation: ReputationApi, pub inbox: InboxApi, pub channels: ChannelsApi, + pub contacts: ContactsApi, pub conversations: ConversationsApi, pub broadcasts: BroadcastsApi, pub bounties: BountiesApi, @@ -133,6 +135,7 @@ impl TinyPlaceClient { reputation: ReputationApi::new(http.clone()), inbox: InboxApi::new(http.clone()), channels: ChannelsApi::new(http.clone()), + contacts: ContactsApi::new(http.clone()), conversations: ConversationsApi::new(http.clone()), broadcasts: BroadcastsApi::new(http.clone()), bounties: BountiesApi::new(http.clone()),