wekan-cli 2.1.0

Agent-first, human-friendly command-line client for Wekan
Documentation
use crate::client::{ClientError, WekanClient, transport::decode_success};
use reqwest::{Method, header::ACCEPT};
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize};
use serde_json::Number;
use std::collections::BTreeMap;

pub const PROBLEM_STREAMS: [&str; 9] = [
    "security",
    "speed",
    "tests",
    "cpu",
    "ram",
    "disk",
    "database",
    "integrity",
    "mail",
];
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProgressProblem {
    pub kind: String,
    pub active: bool,
    pub message: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DetectedProblem {
    pub id: String,
    pub severity: String,
    pub title: String,
    pub detail: String,
    pub count: Option<Number>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NewProblemCount {
    pub stream: String,
    pub count: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(
    deny_unknown_fields,
    rename_all(deserialize = "camelCase", serialize = "snake_case")
)]
pub struct ProblemsOverview {
    pub any_in_progress: bool,
    pub in_progress: Vec<ProgressProblem>,
    pub any_problems: bool,
    pub problems: Vec<DetectedProblem>,
    pub new_problems: Vec<NewProblemCount>,
    pub streams: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EventActor {
    pub kind: String,
    pub value: String,
    pub count: Number,
    pub at: String,
    pub family: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EventLocation {
    pub country: Option<String>,
    pub city: Option<String>,
    pub region: Option<String>,
    pub latitude: Option<Number>,
    pub longitude: Option<Number>,
    pub via: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(
    deny_unknown_fields,
    rename_all(deserialize = "camelCase", serialize = "snake_case")
)]
pub struct ProblemEvent {
    #[serde(rename(deserialize = "_id", serialize = "event_id"))]
    pub event_id: String,
    pub stream: String,
    pub at: String,
    pub severity: Option<String>,
    pub category: Option<String>,
    pub bleed: Option<String>,
    pub action: Option<String>,
    pub source: Option<String>,
    pub cwe: Option<String>,
    pub user_id: Option<String>,
    pub username: Option<String>,
    pub ip: Option<String>,
    pub ipv4: Option<String>,
    pub ipv6: Option<String>,
    pub location: Option<EventLocation>,
    pub count: Option<Number>,
    pub first_at: Option<String>,
    pub actors: Option<BTreeMap<String, EventActor>>,
    pub actors_overflow: Option<Number>,
    pub detail: Option<String>,
    #[serde(rename(deserialize = "type", serialize = "event_type"))]
    pub event_type: Option<String>,
    pub db: Option<String>,
    pub kind: Option<String>,
    pub message: Option<String>,
    pub api: Option<String>,
    pub api_user_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProblemPage {
    pub stream: String,
    pub total: u64,
    pub limit: u64,
    pub skip: u64,
    pub rows: Vec<ProblemEvent>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProblemAcknowledgment {
    pub acknowledged: String,
}

impl WekanClient {
    pub async fn problems_overview(
        &self,
        token: &SecretString,
    ) -> Result<ProblemsOverview, ClientError> {
        self.admin_settings(
            Method::GET,
            "api/admin/problems",
            None,
            token,
            "admin problems overview",
        )
        .await
    }
    pub async fn problem_stream(
        &self,
        stream: &str,
        limit: u64,
        skip: u64,
        search: Option<&str>,
        token: &SecretString,
    ) -> Result<ProblemPage, ClientError> {
        let mut url =
            self.server()
                .join("api/admin/problems")
                .map_err(|_| ClientError::Protocol {
                    message: "could not construct problem stream endpoint".into(),
                    diagnostic: None,
                    success_status_received: false,
                })?;
        url.path_segments_mut()
            .expect("HTTP URL supports path segments")
            .push(stream);
        url.query_pairs_mut()
            .append_pair("limit", &limit.to_string())
            .append_pair("skip", &skip.to_string());
        if let Some(search) = search {
            url.query_pairs_mut().append_pair("search", search);
        }
        let body = self
            .execute_success_request_with_limit(
                self.http
                    .get(url)
                    .header(ACCEPT, "application/json")
                    .bearer_auth(token.expose_secret()),
                64 * 1024 * 1024,
            )
            .await?;
        let page: ProblemPage = decode_success(&body, "admin problem stream", true)?;
        if page.stream != stream {
            return Err(ClientError::Protocol {
                message: "the returned problem stream did not match the requested stream".into(),
                diagnostic: None,
                success_status_received: true,
            });
        }
        Ok(page)
    }
    pub async fn acknowledge_problem_stream(
        &self,
        stream: &str,
        token: &SecretString,
    ) -> Result<ProblemAcknowledgment, ClientError> {
        let mut url =
            self.server()
                .join("api/admin/problems")
                .map_err(|_| ClientError::Protocol {
                    message: "could not construct problem acknowledgment endpoint".into(),
                    diagnostic: None,
                    success_status_received: false,
                })?;
        url.path_segments_mut()
            .expect("HTTP URL supports path segments")
            .extend([stream, "acknowledge"]);
        let body = self
            .execute_success_request(
                self.http
                    .post(url)
                    .header(ACCEPT, "application/json")
                    .bearer_auth(token.expose_secret()),
            )
            .await?;
        let response: ProblemAcknowledgment =
            decode_success(&body, "admin problem acknowledgment", true)?;
        if response.acknowledged != stream {
            return Err(ClientError::Protocol {
                message: "the acknowledged problem stream did not match the request".into(),
                diagnostic: None,
                success_status_received: true,
            });
        }
        Ok(response)
    }
}