Skip to main content

hey_sdk/services/
collections.rs

1//! Making collections and filing threads into them, on top of the generated collection
2//! routes.
3//!
4//! HEY serves no JSON endpoint for any of these, so each one is a browser form post.
5
6use crate::error::Error;
7use crate::generated::types::{CollectionPayload, UpdateCollectionRequestContent};
8use crate::http::Method;
9use crate::services::write_info;
10
11pub use crate::generated::services::collections::*;
12
13/// What a new collection is made of.
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct CreateCollectionParams {
16    /// What the collection is called.
17    pub name: String,
18    /// The blurb shown under the name.
19    pub summary: Option<String>,
20    /// The account that owns it. `None` leaves HEY to pick your first.
21    pub account_id: Option<i64>,
22}
23
24/// What an edit changes about a collection. A field left unset — `None` or empty — is left
25/// off the wire, and HEY leaves what a request does not name alone.
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct UpdateCollectionParams {
28    /// A new name.
29    pub name: Option<String>,
30    /// A new blurb under the name.
31    pub summary: Option<String>,
32}
33
34impl Collections<'_> {
35    /// Makes a collection.
36    ///
37    /// The form post answers with a redirect to the collections index rather than to the
38    /// collection it made, so the new collection's id does not come back.
39    /// [`Collections::list`] afterwards is how to find it.
40    pub async fn create(&self, params: &CreateCollectionParams) -> Result<(), Error> {
41        let account = params.account_id.map(|account_id| account_id.to_string());
42        let mut fields = vec![("collection[name]", params.name.as_str())];
43        if let Some(summary) = params.summary.as_deref().filter(|it| !it.is_empty()) {
44            fields.push(("collection[summary]", summary));
45        }
46        if let Some(account) = &account {
47            fields.push(("account_id", account.as_str()));
48        }
49
50        let mut operation = self.client().form(Method::POST, "/collections")?;
51        operation.info(write_info(
52            "Collections",
53            "CreateCollection",
54            "collection",
55            None,
56        ));
57        operation.form(&fields);
58        self.client().send_unit(operation).await
59    }
60
61    /// Renames a collection or changes its summary. The generated [`Collections::update`]
62    /// takes the same request as a body.
63    pub async fn update_collection(
64        &self,
65        collection_id: i64,
66        params: &UpdateCollectionParams,
67    ) -> Result<(), Error> {
68        let body = UpdateCollectionRequestContent {
69            collection: CollectionPayload {
70                name: present(params.name.as_deref()),
71                summary: present(params.summary.as_deref()),
72            },
73        };
74        self.update(collection_id, &body).await
75    }
76
77    /// Files a topic into a collection.
78    pub async fn add_topic(&self, topic_id: i64, collection_id: i64) -> Result<(), Error> {
79        let mut operation = self
80            .client()
81            .form(Method::POST, &format!("/topics/{topic_id}/collecting"))?;
82        operation.info(write_info(
83            "Collections",
84            "CreateTopicCollecting",
85            "collecting",
86            Some(topic_id),
87        ));
88        operation.query("collection_id", collection_id).form(&[]);
89        self.client().send_unit(operation).await
90    }
91
92    /// Takes a topic back out of a collection. A shadowed topic is silently left alone.
93    pub async fn remove_topic(&self, topic_id: i64, collection_id: i64) -> Result<(), Error> {
94        let mut operation = self
95            .client()
96            .form(Method::DELETE, &format!("/topics/{topic_id}/collecting"))?;
97        operation.info(write_info(
98            "Collections",
99            "DeleteTopicCollecting",
100            "collecting",
101            Some(topic_id),
102        ));
103        operation.query("collection_id", collection_id);
104        self.client().send_unit(operation).await
105    }
106}
107
108/// An empty string is no value, and is left off the wire like a `None` one — the omission
109/// is what tells HEY to leave the field as it is.
110fn present(value: Option<&str>) -> Option<String> {
111    value.filter(|value| !value.is_empty()).map(str::to_string)
112}