Skip to main content

nextcloud_activity/
query.rs

1//! Query filter builder for Nextcloud Activity API requests.
2//!
3//! Official documentation:
4//! - [Activity API Parameters](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/activity-api.html#parameters)
5//! - [Developer manual source](https://github.com/nextcloud/documentation/blob/master/developer_manual/client_apis/activity-api.rst)
6
7use nextcloud_ocs::{OcsFormat, Query};
8
9/// Query builder for filtering activities in the activity stream.
10///
11/// Corresponds to the query parameters accepted by
12/// `GET /ocs/v2.php/apps/activity/api/v2/activity`.
13///
14/// See the [Nextcloud Activity API documentation](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/activity-api.html#parameters)
15/// and [developer_manual source](https://github.com/nextcloud/documentation/blob/master/developer_manual/client_apis/activity-api.rst).
16#[derive(Clone, Debug, Default, PartialEq, Eq)]
17pub struct ActivityFilter {
18    /// ID of the last activity seen (used for pagination to fetch entries relative to this ID).
19    pub since: Option<u64>,
20
21    /// Maximum number of activities to return per page (server default is 50).
22    pub limit: Option<u32>,
23
24    /// Category or type of the target object to filter by (e.g. `"files"`).
25    pub object_type: Option<String>,
26
27    /// Numeric identifier of the target object to filter by.
28    pub object_id: Option<u64>,
29
30    /// Sort direction relative to `since`: `"asc"` or `"desc"` (server default is `"desc"`).
31    pub sort: Option<String>,
32}
33
34impl ActivityFilter {
35    /// Creates a new empty `ActivityFilter`.
36    pub const fn new() -> Self {
37        Self {
38            since: None,
39            limit: None,
40            object_type: None,
41            object_id: None,
42            sort: None,
43        }
44    }
45
46    /// Sets the `since` activity ID threshold for pagination.
47    #[must_use]
48    pub const fn since(mut self, since: u64) -> Self {
49        self.since = Some(since);
50        self
51    }
52
53    /// Sets the maximum number of activities to return.
54    #[must_use]
55    pub const fn limit(mut self, limit: u32) -> Self {
56        self.limit = Some(limit);
57        self
58    }
59
60    /// Sets the object type filter (e.g. `"files"`).
61    #[must_use]
62    pub fn object_type(mut self, object_type: impl Into<String>) -> Self {
63        self.object_type = Some(object_type.into());
64        self
65    }
66
67    /// Sets the object ID filter.
68    #[must_use]
69    pub const fn object_id(mut self, object_id: u64) -> Self {
70        self.object_id = Some(object_id);
71        self
72    }
73
74    /// Convenience helper to set both `object_type` and `object_id` together.
75    #[must_use]
76    pub fn object(mut self, object_type: impl Into<String>, object_id: u64) -> Self {
77        self.object_type = Some(object_type.into());
78        self.object_id = Some(object_id);
79        self
80    }
81
82    /// Sets the sort direction (`"asc"` or `"desc"`).
83    #[must_use]
84    pub fn sort(mut self, sort: impl Into<String>) -> Self {
85        self.sort = Some(sort.into());
86        self
87    }
88
89    /// Converts this filter into an OCS [`Query`] with `format=json` appended.
90    pub fn to_query(&self) -> Query {
91        let mut query = Query::new().format(OcsFormat::Json);
92
93        if let Some(since) = self.since {
94            query = query.param("since", since.to_string());
95        }
96        if let Some(limit) = self.limit {
97            query = query.param("limit", limit.to_string());
98        }
99        if let Some(ref object_type) = self.object_type {
100            query = query.param("object_type", object_type);
101        }
102        if let Some(object_id) = self.object_id {
103            query = query.param("object_id", object_id.to_string());
104        }
105        if let Some(ref sort) = self.sort {
106            query = query.param("sort", sort);
107        }
108
109        query
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn empty_filter_produces_format_json() {
119        let filter = ActivityFilter::new();
120        let query = filter.to_query();
121
122        let params: Vec<(&str, &str)> = query.iter().collect();
123        assert_eq!(params, vec![("format", "json")]);
124    }
125
126    #[test]
127    fn full_filter_builds_all_parameters() {
128        let filter = ActivityFilter::new()
129            .since(123)
130            .limit(25)
131            .object_type("files")
132            .object_id(456)
133            .sort("asc");
134
135        let query = filter.to_query();
136        let params: Vec<(&str, &str)> = query.iter().collect();
137
138        assert_eq!(
139            params,
140            vec![
141                ("format", "json"),
142                ("since", "123"),
143                ("limit", "25"),
144                ("object_type", "files"),
145                ("object_id", "456"),
146                ("sort", "asc"),
147            ]
148        );
149    }
150
151    #[test]
152    fn object_convenience_method_sets_both_fields() {
153        let filter = ActivityFilter::new().object("shares", 789);
154        assert_eq!(filter.object_type.as_deref(), Some("shares"));
155        assert_eq!(filter.object_id, Some(789));
156
157        let query = filter.to_query();
158        let params: Vec<(&str, &str)> = query.iter().collect();
159        assert!(params.contains(&("object_type", "shares")));
160        assert!(params.contains(&("object_id", "789")));
161    }
162}