atlas-confluence 0.2.0

Simple library to interact with the Confluence REST API
Documentation
use base64::{engine::general_purpose, Engine};
use reqwest::{
    header::{HeaderMap, HeaderValue, AUTHORIZATION},
    Client,
};

use log::{error, trace};

use crate::models::{Page, PostPage, Space, SpaceContentResult, SpacesResult};

pub mod models;

pub struct Session {
    base_url: String,
    client: Client,
}

impl Session {
    pub fn new(username: String, api_key: String, base_url: String) -> Session {
        let auth_header_val = general_purpose::STANDARD.encode(format!("{}:{}", username, api_key));

        let mut headers = HeaderMap::new();
        headers.insert(
            AUTHORIZATION,
            HeaderValue::from_str(&format!("Basic {auth_header_val}")).unwrap(),
        );

        let client = reqwest::Client::builder()
            .default_headers(headers)
            .build()
            .unwrap();

        Session { base_url, client }
    }

    pub async fn get_page_by_id(&self, id: u64) -> Result<Page, ()> {
        let url = format!("{}/rest/api/content/{}", self.base_url, id);
        trace!("GET {}", url);
        let response = self
            .client
            .get(url)
            .query(&[("expand", "body.view,space,children.page,version")])
            .send()
            .await
            .unwrap();
        if response.status().is_client_error() || response.status().is_server_error() {
            error!("Error getting page: {}", response.text().await.unwrap());
            return Err(());
        }
        let page: Page = serde_json::from_str(&response.text().await.unwrap()).unwrap();
        Ok(page)
    }

    pub async fn get_spaces(&self) -> Result<Vec<Space>, ()> {
        let url = format!("{}/rest/api/space", self.base_url);
        trace!("GET {}", url);
        let response = self
            .client
            .get(url)
            .query(&[("type", "global")])
            .send()
            .await
            .unwrap();
        if response.status().is_client_error() || response.status().is_server_error() {
            error!("Error getting page: {}", response.text().await.unwrap());
            return Err(());
        }
        let result: SpacesResult = serde_json::from_str(&response.text().await.unwrap()).unwrap();
        Ok(result.results)
    }

    #[async_recursion::async_recursion]
    pub async fn get_pages_for_space(
        &self,
        space_key: &str,
        next: Option<String>,
    ) -> Result<Vec<Page>, ()> {
        let url = if next.is_some() {
            format!("{}{}", self.base_url, next.unwrap().replace("/page", ""))
        } else {
            format!("{}/rest/api/space/{}/content", self.base_url, space_key)
        };
        trace!("GET {}", url);
        let response = self
            .client
            .get(url)
            .query(&[("expand", "body.view,space,children.page,version")])
            .send()
            .await
            .unwrap();
        if response.status().is_client_error() || response.status().is_server_error() {
            error!("Error getting page: {}", response.text().await.unwrap());
            return Err(());
        }
        let result: SpaceContentResult =
            serde_json::from_str(&response.text().await.unwrap()).unwrap();
        let mut pages = result.page.results;
        if result.page.links.next.is_some() {
            let mut next_pages = self
                .get_pages_for_space(space_key, result.page.links.next)
                .await
                .unwrap();
            pages.append(&mut next_pages);
            return Ok(pages);
        }
        Ok(pages)
    }

    pub async fn add_new_page(
        &self,
        space_key: String,
        ancestor: Option<u64>,
        title: String,
        body: Option<String>,
    ) -> Result<Page, ()> {
        let url = format!("{}/rest/api/content", self.base_url);
        trace!("POST {}", url);
        let response = self
            .client
            .post(url)
            .query(&[("expand", "body.view,space,children.page,version")])
            .json(&PostPage::new_new_page(title, ancestor, space_key, body))
            .send()
            .await
            .unwrap();
        if response.status().is_client_error() || response.status().is_server_error() {
            error!("Error adding new page: {}", response.text().await.unwrap());
            return Err(());
        }
        let page: Page = serde_json::from_str(&response.text().await.unwrap()).unwrap();
        Ok(page)
    }

    pub async fn update_page(
        &self,
        space_key: String,
        id: u64,
        new_version: u64,
        title: String,
        body: Option<String>,
    ) -> Result<Page, ()> {
        let url = format!("{}/rest/api/content/{}", self.base_url, id);
        trace!("PUT {}", url);
        let response = self
            .client
            .put(url)
            .query(&[("expand", "body.view,space,children.page,version")])
            .json(&PostPage::new_update_page(
                id,
                title,
                space_key,
                body,
                new_version,
            ))
            .send()
            .await
            .unwrap();
        if response.status().is_client_error() || response.status().is_server_error() {
            error!("Error updating page: {}", response.text().await.unwrap());
            return Err(());
        }
        let page: Page = serde_json::from_str(&response.text().await.unwrap()).unwrap();
        Ok(page)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_session_creation() {
        let _session = Session::new("".to_owned(), "".to_owned(), "".to_owned());
    }
}