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 self.require_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 self.require_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 fn require_token(&self) -> Result<()> {
143 if self.token.is_none() {
144 return Err(KickApiError::ApiError(
145 "OAuth token required for this endpoint".to_string(),
146 ));
147 }
148 Ok(())
149 }
150
151 async fn parse_response<T: serde::de::DeserializeOwned>(
152 &self,
153 response: reqwest::Response,
154 ) -> Result<Vec<T>> {
155 if response.status().is_success() {
156 let body = response.text().await?;
157
158 #[derive(serde::Deserialize)]
159 struct DataResponse<T> {
160 data: Vec<T>,
161 }
162
163 let resp: DataResponse<T> = serde_json::from_str(&body)
164 .map_err(|e| KickApiError::ApiError(format!("JSON parse error: {}", e)))?;
165
166 Ok(resp.data)
167 } else {
168 Err(KickApiError::ApiError(format!(
169 "Request failed: {}",
170 response.status()
171 )))
172 }
173 }
174}