resourcespace-client 0.1.0

A Rust client for the communicating with ResourceSpace API
Documentation
use serde::Serialize;
use serde_with::json::JsonString;
use serde_with::{serde_as, skip_serializing_none};

use crate::client::Client;
use crate::error::RsError;

use super::List;

#[derive(Debug)]
pub struct UserApi<'a> {
    client: &'a Client,
}

/// Sub-API for user endpoints.
impl<'a> UserApi<'a> {
    pub(crate) fn new(client: &'a Client) -> Self {
        Self { client }
    }

    /// Find out if the current user has a particular permission. The permission strings are shown in the ResourceSpace UI when managing group permissions.
    ///
    /// ## Arguments
    /// * `request` - Parameters built via [`CheckpermRequest`]
    ///
    /// ## Returns
    ///
    /// TRUE if the user has the permission, FALSE if they don't.
    ///
    /// ## TODO: Errors
    ///
    /// ## TODO: Examples
    pub async fn checkperm(&self, request: CheckpermRequest) -> Result<serde_json::Value, RsError> {
        self.client
            .send_request("checkperm", reqwest::Method::GET, request)
            .await
    }

    /// Retrieve a list of users
    ///
    /// Permissions are always honoured so users from other groups to which this user does not have access will be omitted.
    ///
    /// ## Arguments
    /// * `request` - Parameters built via [`GetUsersRequest`]
    ///
    /// ## Returns
    ///
    /// An array of matching user records include ID ("ref"), username, full name and user group ID.
    ///
    /// ## TODO: Errors
    ///
    /// ## TODO: Examples
    pub async fn get_users(&self, request: GetUsersRequest) -> Result<serde_json::Value, RsError> {
        self.client
            .send_request("get_users", reqwest::Method::GET, request)
            .await
    }

    /// Retrieve information on all users with the given permissions
    ///
    /// Permissions are always honoured so users from groups to which this user does not have access will be omitted.
    ///
    /// ## Arguments
    /// * `request` - Parameters built via [`GetUsersByPermissionRequest`]
    ///
    /// ## Returns
    ///
    /// An array of matching user records with a subset of information from the user record
    ///
    /// ## TODO: Errors
    ///
    /// ## TODO: Examples
    pub async fn get_users_by_permission(
        &self,
        request: GetUsersByPermissionRequest,
    ) -> Result<serde_json::Value, RsError> {
        self.client
            .send_request("get_users_by_permission", reqwest::Method::GET, request)
            .await
    }

    /// Mark a specified email address as invalid.
    ///
    /// Email addresses marked as invalid will be blocked before send_mail() tries to dispatch any emails, this will be applied to any users with this email address.
    ///
    /// ## Arguments
    /// * `request` - Parameters built via [`GetUsersByPermissionRequest`]
    ///
    /// ## Returns
    ///
    /// Boolean - true if one or more users are found and mark as having invalid adresses, false otherwise.
    ///
    /// ## TODO: Errors
    ///
    /// ## TODO: Examples
    pub async fn mark_email_as_invalid(
        &self,
        request: MarkEmailAsInvalidRequest,
    ) -> Result<serde_json::Value, RsError> {
        self.client
            .send_request("mark_email_as_invalid", reqwest::Method::POST, request)
            .await
    }

    /// Save a user record.
    ///
    /// Use [`new_user`](Self::new_user) first to create the user, then call this with the
    /// returned ID to populate the user's details.
    ///
    /// ## Arguments
    /// * `request` - Parameters built via [`SaveUserRequest`]
    ///
    /// ## Returns
    ///
    /// Returns `Ok(())` on success (HTTP 200). Returns an error on HTTP 409 (e.g. missing
    /// required fields) or HTTP 403 (permission denied).
    ///
    /// ## TODO: Errors
    ///
    /// ## TODO: Examples
    pub async fn save_user(&self, request: SaveUserRequest) -> Result<serde_json::Value, RsError> {
        self.client
            .send_request("save_user", reqwest::Method::POST, request)
            .await
    }

    /// Create a new user record.
    ///
    /// Create a user record. Use the returned ID to then call save_user() with the user details.
    ///
    /// ## Arguments
    /// * `request` - Parameters built via [`NewUserRequest`]
    ///
    /// ## Returns
    ///
    /// The new user ID in `data.ref` on success (HTTP 200).
    /// HTTP 409 if the username already exists (`data.ref = false`) or the user limit has
    /// been reached (`data.ref = -2`). HTTP 403 on permission failure.
    ///
    /// ## TODO: Errors
    ///
    /// ## TODO: Examples
    pub async fn new_user(&self, request: NewUserRequest) -> Result<serde_json::Value, RsError> {
        self.client
            .send_request("new_user", reqwest::Method::POST, request)
            .await
    }
}

#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct CheckpermRequest {
    /// The permission string to check (e.g. `"a"` for admin, `"e"` for edit).
    pub perm: String,
}

impl CheckpermRequest {
    pub fn new(perm: impl Into<String>) -> Self {
        Self { perm: perm.into() }
    }
}

#[skip_serializing_none]
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
pub struct GetUsersRequest {
    /// Search string to filter users by name or username.
    pub find: Option<String>,
    /// If set, only returns users whose username exactly matches `find`.
    pub exact_username_match: Option<bool>,
}

impl GetUsersRequest {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn find(mut self, find: impl Into<String>) -> Self {
        self.find = Some(find.into());
        self
    }

    pub fn exact_username_match(mut self, exact_username_match: bool) -> Self {
        self.exact_username_match = Some(exact_username_match);
        self
    }
}

#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct GetUsersByPermissionRequest {
    /// List of permission strings; only users holding all of these are returned.
    pub permissions: List<String>,
}

impl GetUsersByPermissionRequest {
    pub fn new(permissions: impl Into<List<String>>) -> Self {
        Self {
            permissions: permissions.into(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct MarkEmailAsInvalidRequest {
    /// The email address to mark as invalid.
    pub email: String,
}

impl MarkEmailAsInvalidRequest {
    pub fn new(email: impl Into<String>) -> Self {
        Self {
            email: email.into(),
        }
    }
}

#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct NewUserRequest {
    /// The username for the new user account.
    pub username: String,
    /// The ID of the user group to assign this user to.
    pub usergroup: Option<u32>,
}

impl NewUserRequest {
    pub fn new(username: impl Into<String>) -> Self {
        Self {
            username: username.into(),
            usergroup: None,
        }
    }

    pub fn usergroup(mut self, usergroup: u32) -> Self {
        self.usergroup = Some(usergroup);
        self
    }
}

#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct SaveUserRequest {
    /// The ID of the user to update.
    #[serde(rename = "ref")]
    pub r#ref: u32,
    /// JSON object containing the user fields to save (e.g. fullname, email, usergroup).
    #[serde_as(as = "JsonString")]
    pub data: SaveUserData,
}

impl SaveUserRequest {
    pub fn new(r#ref: u32, data: SaveUserData) -> Self {
        Self { r#ref, data }
    }
}

#[skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct SaveUserData {
    /// Username used to log into the account.
    pub username: Option<String>,
    /// Password for the account in plain text.
    pub password: Option<String>,
    /// Full display name of the user.
    pub fullname: Option<String>,
    /// Email address associated with the account.
    pub email: Option<String>,
    /// ID of the user group to assign the user to.
    pub usergroup: Option<u32>,
    /// Optional IP restriction for the account. Can contain a single IP, or a wildcard pattern.
    pub ip_restrict: Option<String>,
    // pub search_filter_override: ?
    // pub search_filter_o_id: ?
    /// Administrative comments or notes about the user.
    pub comments: Option<String>,
    /// Whether the user should receive content suggestions.
    pub suggest: Option<bool>,
    /// Whether to send the user a password reset link by email instead of setting a password directly.
    pub emailresetlink: Option<bool>,
    /// Approval state of the account.
    pub approved: Option<u8>,
    /// Account expiry date in `YYYY-MM-DD` format, e.g. `"2026-12-31"`.
    pub expires: Option<String>,
}