parse_rs/
session.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use crate::error::ParseError;
6use crate::object::{deserialize_string_to_option_parse_date, deserialize_string_to_parse_date};
7use crate::types::ParseDate;
8use crate::Parse;
9
10/// Represents a Parse Server Session object, detailing an active user session.
11///
12/// This struct includes standard fields for a session such as `objectId`, `sessionToken`,
13/// `createdAt`, `updatedAt`, `expiresAt`, and information about the associated `user`.
14/// It also captures `installationId` if the session originated from a specific installation,
15/// and `createdWith` which describes how the session was initiated (e.g., login, signup).
16///
17/// The `user` field is a `serde_json::Value` because its content can vary. If the query
18/// fetching the session includes `include=user`, this field will contain the full `ParseUser` object.
19/// Otherwise, it might be a pointer or a more minimal representation.
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub struct ParseSession {
22    #[serde(rename = "objectId")]
23    pub object_id: String,
24
25    #[serde(deserialize_with = "deserialize_string_to_parse_date")]
26    #[serde(rename = "createdAt")]
27    pub created_at: ParseDate,
28
29    #[serde(default)] // Default will be Option::None
30    #[serde(deserialize_with = "deserialize_string_to_option_parse_date")]
31    #[serde(rename = "updatedAt")]
32    pub updated_at: Option<ParseDate>,
33
34    /// The user associated with this session. Can be a full `ParseUser` object if `include=user` is used.
35    pub user: Value, // When 'include=user' is used, this will be the full User object
36
37    #[serde(rename = "sessionToken")]
38    pub session_token: String,
39
40    #[serde(rename = "installationId")]
41    pub installation_id: Option<String>,
42
43    #[serde(rename = "expiresAt")]
44    #[serde(default)] // Default will be Option::None
45    pub expires_at: Option<ParseDate>,
46
47    pub restricted: Option<bool>, // Typically false for normal sessions, true for restricted ones
48
49    #[serde(rename = "createdWith")]
50    pub created_with: Option<Value>, // e.g., {"action": "login", "authProvider": "password"}
51
52    // Catch all for other fields
53    #[serde(flatten)]
54    pub other_fields: std::collections::HashMap<String, Value>,
55}
56
57/// Represents the successful response from a session update operation.
58///
59/// When a session is updated via the API (e.g., using `ParseSessionHandle::update_by_object_id`),
60/// the server typically responds with the `updatedAt` timestamp of the modified session.
61#[derive(Debug, Serialize, Deserialize, Clone)]
62#[serde(rename_all = "camelCase")]
63pub struct SessionUpdateResponse {
64    /// The timestamp indicating when the session was last updated.
65    pub updated_at: String,
66}
67
68/// Represents the response structure when fetching multiple sessions.
69///
70/// This struct is used to deserialize the JSON array of session objects returned by endpoints
71/// like `/parse/sessions` when queried with the Master Key.
72#[derive(Debug, Deserialize, Clone)]
73pub struct GetAllSessionsResponse {
74    /// A vector containing the [`ParseSession`](crate::session::ParseSession) objects retrieved.
75    pub results: Vec<ParseSession>,
76    // Optionally, add `count: Option<i64>` if count is requested and needed.
77}
78
79/// Provides methods for interacting with Parse Server sessions.
80///
81/// An instance of `ParseSessionHandle` is obtained by calling the [`session()`](crate::Parse::session)
82/// method on a `Parse` instance. It allows for operations such as retrieving the current session's details,
83/// fetching specific sessions by ID (Master Key required), deleting sessions (Master Key required),
84/// updating sessions (Master Key required), and listing all sessions (Master Key required).
85///
86/// This handle operates in the context of the `Parse` it was created from, using its configuration
87/// (server URL, app ID, keys) for API requests.
88pub struct ParseSessionHandle<'a> {
89    client: &'a Parse,
90}
91
92impl<'a> ParseSessionHandle<'a> {
93    /// Creates a new `ParseSessionHandle`.
94    ///
95    /// This constructor is typically called by `Parse::session()`.
96    ///
97    /// # Arguments
98    ///
99    /// * `client`: A reference to the `Parse` instance that this handle will operate upon.
100    pub fn new(client: &'a Parse) -> Self {
101        ParseSessionHandle { client }
102    }
103
104    /// Retrieves the current user's session details.
105    ///
106    /// This method makes a GET request to the `/sessions/me` endpoint. It requires an active
107    /// session token to be configured in the `Parse` (typically set after a successful
108    /// login or signup). The server then returns the full details of the session associated
109    /// with that token.
110    ///
111    /// If no session token is available in the client, this method will return a
112    /// `ParseError::SessionTokenMissing` error without making a network request.
113    ///
114    /// # Returns
115    ///
116    /// A `Result` containing the [`ParseSession`](crate::session::ParseSession) object for the current session
117    /// if successful, or a `ParseError` if the request fails (e.g., session token invalid/expired,
118    /// network issue, or no session token present).
119    ///
120    /// # Examples
121    ///
122    /// ```rust,no_run
123    /// use parse_rs::{Parse, ParseError, user::LoginRequest};
124    ///
125    /// # #[tokio::main]
126    /// # async fn main() -> Result<(), ParseError> {
127    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
128    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
129    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
130    ///
131    /// // Assume a user has logged in, so client.session_token() is Some(...)
132    /// // let login_details = LoginRequest { username: "test_user", password: "password123" };
133    /// // client.user().login(&login_details).await?;
134    ///
135    /// if client.is_authenticated() {
136    ///     match client.session().me().await {
137    ///         Ok(current_session) => {
138    ///             println!("Current session details retrieved for user: {:?}", current_session.user);
139    ///             println!("Session token: {}", current_session.session_token);
140    ///             println!("Expires at: {:?}", current_session.expires_at);
141    ///             // The session token in current_session should match client.session_token()
142    ///             assert_eq!(Some(current_session.session_token.as_str()), client.session_token());
143    ///         }
144    ///         Err(e) => eprintln!("Failed to get current session details: {}", e),
145    ///     }
146    /// } else {
147    ///     println!("No user is currently authenticated to get session details.");
148    /// }
149    /// # Ok(())
150    /// # }
151    /// ```
152    // Corresponds to GET /parse/sessions/me
153    pub async fn me(&self) -> Result<ParseSession, ParseError> {
154        if self.client.session_token.is_none() {
155            return Err(ParseError::SessionTokenMissing);
156        }
157        // GET /sessions/me does not take a body and uses the client's current session token for auth.
158        self.client
159            ._request(Method::GET, "sessions/me", None::<&Value>, false, None) // false for use_master_key, None for explicit session_token (uses client's)
160            .await
161    }
162
163    /// Retrieves a specific session by its `objectId`.
164    ///
165    /// This method makes a GET request to the `/sessions/:objectId` endpoint. It requires the
166    /// Master Key to be configured in the `Parse` for authorization, as accessing arbitrary
167    /// session objects is a privileged operation.
168    ///
169    /// # Arguments
170    ///
171    /// * `object_id`: A string slice representing the `objectId` of the session to retrieve.
172    ///
173    /// # Returns
174    ///
175    /// A `Result` containing the [`ParseSession`](crate::session::ParseSession) object if found and the Master Key is valid,
176    /// or a `ParseError` if the session is not found, the Master Key is missing or invalid, or any
177    /// other error occurs (e.g., network issue).
178    ///
179    /// # Examples
180    ///
181    /// ```rust,no_run
182    /// use parse_rs::{Parse, ParseError};
183    ///
184    /// # #[tokio::main]
185    /// # async fn main() -> Result<(), ParseError> {
186    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
187    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
188    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
189    /// // Ensure the client is initialized with the Master Key for this operation
190    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
191    ///
192    /// let session_object_id_to_fetch = "someValidSessionObjectId"; // Replace with an actual session objectId
193    ///
194    /// match client.session().get_by_object_id(session_object_id_to_fetch).await {
195    ///     Ok(session) => {
196    ///         println!("Successfully fetched session with objectId: {}", session.object_id);
197    ///         println!("Associated user: {:?}", session.user);
198    ///         println!("Session token: {}", session.session_token);
199    ///     }
200    ///     Err(e) => {
201    ///         eprintln!("Failed to get session by objectId '{}': {}", session_object_id_to_fetch, e);
202    ///         // This could be due to various reasons: session not found, master key invalid, network error, etc.
203    ///         // e.g., if master key is missing or wrong, ParseError::MasterKeyMissingOrInvalid might be returned by the server (as unauthorized).
204    ///         // e.g., if session_object_id_to_fetch does not exist, ParseError::ObjectNotFound might be returned.
205    ///     }
206    /// }
207    /// # Ok(())
208    /// # }
209    /// ```
210    pub async fn get_by_object_id(&self, object_id: &str) -> Result<ParseSession, ParseError> {
211        let endpoint = format!("sessions/{}", object_id);
212        self.client
213            ._request(Method::GET, &endpoint, None::<&Value>, true, None) // true for use_master_key
214            .await
215    }
216
217    /// Deletes a specific session by its `objectId`.
218    ///
219    /// This method makes a DELETE request to the `/sessions/:objectId` endpoint. It requires the
220    /// Master Key to be configured in the `Parse` for authorization, as deleting arbitrary
221    /// session objects is a privileged operation. Successfully deleting a session effectively
222    /// invalidates that session token, forcing the user to log in again.
223    ///
224    /// # Arguments
225    ///
226    /// * `object_id`: A string slice representing the `objectId` of the session to delete.
227    ///
228    /// # Returns
229    ///
230    /// A `Result` containing `()` (an empty tuple) if the session is successfully deleted.
231    /// Returns a `ParseError` if the session is not found, the Master Key is missing or invalid,
232    /// or any other error occurs (e.g., network issue).
233    ///
234    /// # Examples
235    ///
236    /// ```rust,no_run
237    /// use parse_rs::{Parse, ParseError};
238    ///
239    /// # #[tokio::main]
240    /// # async fn main() -> Result<(), ParseError> {
241    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
242    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
243    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
244    /// // Ensure the client is initialized with the Master Key for this operation
245    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
246    ///
247    /// let session_object_id_to_delete = "someSessionObjectIdToDelete"; // Replace with an actual session objectId
248    ///
249    /// match client.session().delete_by_object_id(session_object_id_to_delete).await {
250    ///     Ok(_) => {
251    ///         println!("Successfully deleted session with objectId: {}", session_object_id_to_delete);
252    ///     }
253    ///     Err(e) => {
254    ///         eprintln!("Failed to delete session by objectId '{}': {}", session_object_id_to_delete, e);
255    ///         // Common errors: ParseError::ObjectNotFound if the session doesn't exist,
256    ///         // ParseError::MasterKeyMissingOrInvalid (or generic unauthorized) if master key is wrong.
257    ///     }
258    /// }
259    /// # Ok(())
260    /// # }
261    /// ```
262    pub async fn delete_by_object_id(&self, object_id: &str) -> Result<(), ParseError> {
263        let endpoint = format!("sessions/{}", object_id);
264        // Expect serde_json::Value to consume the empty {} response, then map to Ok(()).
265        // The master key is required for this operation.
266        let _: Value = self
267            .client
268            ._request(Method::DELETE, &endpoint, None::<&Value>, true, None) // true for use_master_key
269            .await?;
270        Ok(())
271    }
272
273    /// Updates a specific session by its `objectId`.
274    ///
275    /// This method makes a PUT request to the `/sessions/:objectId` endpoint, allowing modifications
276    /// to the specified session object. It requires the Master Key to be configured in the `Parse`
277    /// for authorization. The `session_data` argument should be a serializable struct or map
278    /// containing the fields to be updated on the session object.
279    ///
280    /// Note: Not all fields on a session object are typically mutable. Consult the Parse Server
281    /// documentation for details on which fields can be updated.
282    ///
283    /// # Type Parameters
284    ///
285    /// * `T`: The type of the `session_data` argument. This type must implement `Serialize`, `Send`, and `Sync`.
286    ///   It can be a custom struct representing the updatable fields or a `serde_json::Value` for dynamic updates.
287    ///
288    /// # Arguments
289    ///
290    /// * `object_id`: A string slice representing the `objectId` of the session to update.
291    /// * `session_data`: A reference to the data containing the fields to update.
292    ///
293    /// # Returns
294    ///
295    /// A `Result` containing a [`SessionUpdateResponse`](crate::session::SessionUpdateResponse) (which typically includes the `updatedAt` timestamp)
296    /// if the session is successfully updated. Returns a `ParseError` if the session is not found,
297    /// the Master Key is missing or invalid, the update data is invalid, or any other error occurs.
298    ///
299    /// # Examples
300    ///
301    /// ```rust,no_run
302    /// use parse_rs::{Parse, ParseError, session::SessionUpdateResponse};
303    /// use serde_json::json; // For creating a JSON value to update
304    ///
305    /// # #[tokio::main]
306    /// # async fn main() -> Result<(), ParseError> {
307    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
308    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
309    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
310    /// // Client must be initialized with the Master Key
311    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
312    ///
313    /// let session_object_id_to_update = "someSessionObjectIdToUpdate"; // Replace with an actual session objectId
314    ///
315    /// // Example: Update a custom field on the session. Parse Server must be configured to allow this.
316    /// // Let's assume we want to add/update a field `customData: { "notes": "privileged update" }`
317    /// let update_payload = json!({
318    ///     "customData": {
319    ///         "notes": "privileged update by master key"
320    ///     }
321    /// });
322    ///
323    /// match client.session().update_by_object_id(session_object_id_to_update, &update_payload).await {
324    ///     Ok(response) => {
325    ///         println!(
326    ///             "Successfully updated session '{}'. New updatedAt: {}",
327    ///             session_object_id_to_update,
328    ///             response.updated_at
329    ///         );
330    ///     }
331    ///     Err(e) => {
332    ///         eprintln!(
333    ///             "Failed to update session by objectId '{}': {}",
334    ///             session_object_id_to_update, e
335    ///         );
336    ///         // Common errors: ParseError::ObjectNotFound, ParseError::MasterKeyMissingOrInvalid, or invalid update payload.
337    ///     }
338    /// }
339    /// # Ok(())
340    /// # }
341    /// ```
342    pub async fn update_by_object_id<T: Serialize + Send + Sync>(
343        &self,
344        object_id: &str,
345        session_data: &T,
346    ) -> Result<SessionUpdateResponse, ParseError> {
347        let endpoint = format!("sessions/{}", object_id);
348        self.client
349            ._request(Method::PUT, &endpoint, Some(session_data), true, None) // true for use_master_key
350            .await
351    }
352
353    /// Retrieves multiple sessions, optionally filtered and paginated using a query string.
354    ///
355    /// This method makes a GET request to the `/sessions` endpoint. It requires the Master Key
356    /// to be configured in the `Parse` for authorization, as listing all sessions is a
357    /// highly privileged operation.
358    ///
359    /// The `query_string` argument allows for server-side filtering, pagination, ordering, and
360    /// inclusion of related data (like the `user` object via `include=user`).
361    ///
362    /// # Arguments
363    ///
364    /// * `query_string`: An optional string slice representing the URL-encoded query parameters.
365    ///   For example: `"limit=10&skip=20&include=user&where={\"user\":{\"$inQuery\":{\"where\":{\"username\":\"test_user\"},\"className\":\"_User\"}}}"`
366    ///   If `None`, all sessions (up to the server's default limit) are requested.
367    ///
368    /// # Returns
369    ///
370    /// A `Result` containing a `Vec<ParseSession>` if the request is successful. The vector will
371    /// contain the session objects matching the query. Returns a `ParseError` if the Master Key
372    /// is missing or invalid, the query string is malformed, or any other error occurs.
373    ///
374    /// # Examples
375    ///
376    /// ```rust,no_run
377    /// use parse_rs::{Parse, ParseError};
378    ///
379    /// # #[tokio::main]
380    /// # async fn main() -> Result<(), ParseError> {
381    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
382    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
383    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
384    /// // Client must be initialized with the Master Key
385    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
386    ///
387    /// // Example 1: Get all sessions (respecting server default limit) and include user data
388    /// match client.session().get_all_sessions(Some("include=user")).await {
389    ///     Ok(sessions) => {
390    ///         println!("Successfully retrieved {} sessions (with user data):", sessions.len());
391    ///         for session in sessions {
392    ///             println!("  Session ID: {}, User: {:?}", session.object_id, session.user);
393    ///         }
394    ///     }
395    ///     Err(e) => eprintln!("Failed to get all sessions with user data: {}", e),
396    /// }
397    ///
398    /// // Example 2: Get the first 5 sessions, ordered by creation date descending
399    /// let query_params = "limit=5&order=-createdAt";
400    /// match client.session().get_all_sessions(Some(query_params)).await {
401    ///     Ok(sessions) => {
402    ///         println!("\nSuccessfully retrieved {} sessions (first 5, newest first):", sessions.len());
403    ///         for session in sessions {
404    ///             println!("  Session ID: {}, Created At: {:?}", session.object_id, session.created_at);
405    ///         }
406    ///     }
407    ///     Err(e) => eprintln!("Failed to get paginated/ordered sessions: {}", e),
408    /// }
409    ///
410    /// // Example 3: Get sessions without any query parameters (server defaults apply)
411    /// match client.session().get_all_sessions(None).await {
412    ///     Ok(sessions) => {
413    ///         println!("\nSuccessfully retrieved {} sessions (server defaults):", sessions.len());
414    ///     }
415    ///     Err(e) => eprintln!("Failed to get sessions with no query: {}", e),
416    /// }
417    /// # Ok(())
418    /// # }
419    /// ```
420    pub async fn get_all_sessions(
421        &self,
422        query_string: Option<&str>,
423    ) -> Result<Vec<ParseSession>, ParseError> {
424        #[derive(Deserialize, Debug)]
425        struct SessionsResponse<T> {
426            results: Vec<T>,
427        }
428
429        let endpoint = match query_string {
430            Some(qs) => format!("sessions?{}", qs),
431            None => "sessions".to_string(),
432        };
433        let response: SessionsResponse<ParseSession> = self
434            .client
435            ._request(Method::GET, &endpoint, None::<&Value>, true, None) // true for use_master_key
436            .await?;
437        Ok(response.results)
438    }
439}