Skip to main content

kick_api/api/
users.rs

1use crate::error::{KickApiError, Result};
2use crate::models::{TokenIntrospection, User};
3use reqwest;
4
5/// Users API - handles all user-related endpoints
6pub struct UsersApi<'a> {
7    client: &'a reqwest::Client,
8    token: &'a Option<String>,
9    base_url: &'a str,
10}
11
12impl<'a> UsersApi<'a> {
13    /// Create a new UsersApi instance
14    pub(crate) fn new(
15        client: &'a reqwest::Client,
16        token: &'a Option<String>,
17        base_url: &'a str,
18    ) -> Self {
19        Self {
20            client,
21            token,
22            base_url,
23        }
24    }
25
26    /// Get users by their IDs
27    ///
28    /// If no IDs are provided, returns the authenticated user's information.
29    ///
30    /// Requires OAuth token with `user:read` scope
31    ///
32    /// # Example
33    /// ```no_run
34    /// // Get specific users
35    /// let users = client.users().get(vec![123, 456]).await?;
36    ///
37    /// // Get current authenticated user
38    /// let me = client.users().get_me().await?;
39    /// ```
40    pub async fn get(&self, user_ids: Vec<u64>) -> Result<Vec<User>> {
41        super::require_token(self.token)?;
42
43        let url = format!("{}/users", self.base_url);
44        let mut request = self
45            .client
46            .get(&url)
47            .header("Accept", "*/*")
48            .bearer_auth(self.token.as_ref().unwrap());
49
50        // If IDs provided, add them as separate query params
51        // Format: ?id=123&id=456 (not comma-separated)
52        if !user_ids.is_empty() {
53            for id in user_ids {
54                request = request.query(&[("id", id)]);
55            }
56        }
57
58        let response = crate::http::send_with_retry(self.client, request).await?;
59        self.parse_response(response).await
60    }
61
62    /// Get the currently authenticated user's information
63    ///
64    /// This is a convenience method that calls `get()` with no IDs.
65    ///
66    /// Requires OAuth token with `user:read` scope
67    ///
68    /// # Example
69    /// ```no_run
70    /// let me = client.users().get_me().await?;
71    /// println!("Logged in as: {}", me.name);
72    /// ```
73    pub async fn get_me(&self) -> Result<User> {
74        let users = self.get(vec![]).await?;
75        users
76            .into_iter()
77            .next()
78            .ok_or_else(|| KickApiError::ApiError("No user data returned".to_string()))
79    }
80
81    /// Introspect an OAuth token (validate it)
82    ///
83    /// This validates the token passed in the Authorization header.
84    /// Implements RFC 7662 OAuth 2.0 Token Introspection.
85    ///
86    /// **Note:** This endpoint is deprecated but still functional.
87    ///
88    /// Requires OAuth token (no specific scope needed)
89    ///
90    /// # Example
91    /// ```no_run
92    /// let introspection = client.users().introspect_token().await?;
93    ///
94    /// if introspection.is_active() {
95    ///     println!("Token is valid!");
96    ///     println!("Scopes: {:?}", introspection.scopes());
97    ///
98    ///     if introspection.has_scope("user:read") {
99    ///         println!("Has user:read permission");
100    ///     }
101    ///
102    ///     if introspection.is_expired() {
103    ///         println!("Token is expired!");
104    ///     }
105    /// } else {
106    ///     println!("Token is invalid");
107    /// }
108    /// ```
109    pub async fn introspect_token(&self) -> Result<TokenIntrospection> {
110        super::require_token(self.token)?;
111
112        let url = format!("{}/token/introspect", self.base_url);
113        let request = self
114            .client
115            .post(&url)
116            .header("Accept", "*/*")
117            .bearer_auth(self.token.as_ref().unwrap());
118        let response = crate::http::send_with_retry(self.client, request).await?;
119
120        if response.status().is_success() {
121            let body = response.text().await?;
122
123            #[derive(serde::Deserialize)]
124            struct IntrospectResponse {
125                data: TokenIntrospection,
126            }
127
128            let resp: IntrospectResponse = serde_json::from_str(&body)
129                .map_err(|e| KickApiError::ApiError(format!("JSON parse error: {}", e)))?;
130
131            Ok(resp.data)
132        } else {
133            Err(KickApiError::ApiError(format!(
134                "Token introspection failed: {}",
135                response.status()
136            )))
137        }
138    }
139
140    // Helper methods
141
142    async fn parse_response<T: serde::de::DeserializeOwned>(
143        &self,
144        response: reqwest::Response,
145    ) -> Result<Vec<T>> {
146        if response.status().is_success() {
147            let body = response.text().await?;
148
149            #[derive(serde::Deserialize)]
150            struct DataResponse<T> {
151                data: Vec<T>,
152            }
153
154            let resp: DataResponse<T> = serde_json::from_str(&body)
155                .map_err(|e| KickApiError::ApiError(format!("JSON parse error: {}", e)))?;
156
157            Ok(resp.data)
158        } else {
159            Err(KickApiError::ApiError(format!(
160                "Request failed: {}",
161                response.status()
162            )))
163        }
164    }
165}