use jc_core::{Client, Result};
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Deserialize)]
pub struct Page {
pub id: String,
pub title: String,
#[serde(rename = "spaceId")]
pub space_id: String,
#[serde(rename = "parentId", default)]
pub parent_id: Option<String>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub version: Option<Version>,
#[serde(default)]
pub body: Option<PageBody>,
#[serde(rename = "authorId", default)]
pub author_id: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct Version {
pub number: u64,
#[serde(rename = "createdAt", default)]
pub created_at: Option<String>,
#[serde(rename = "authorId", default)]
pub author_id: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct PageBody {
#[serde(default, rename = "atlas_doc_format")]
pub atlas_doc_format: Option<BodyValue>,
}
#[derive(Debug, Deserialize)]
pub struct BodyValue {
pub representation: String,
pub value: String,
}
impl PageBody {
pub fn as_adf(&self) -> Option<Value> {
self.atlas_doc_format
.as_ref()
.and_then(|b| serde_json::from_str(&b.value).ok())
}
}
#[derive(Debug, Deserialize)]
pub struct PageSummary {
pub id: String,
pub title: String,
#[serde(rename = "spaceId", default)]
pub space_id: Option<String>,
#[serde(rename = "parentId", default)]
pub parent_id: Option<String>,
#[serde(default)]
pub status: Option<String>,
#[serde(default, rename = "createdAt")]
pub created_at: Option<String>,
}
#[derive(Debug, Deserialize)]
struct PageList {
#[serde(default)]
results: Vec<PageSummary>,
#[serde(rename = "_links", default)]
links: Option<Links>,
}
#[derive(Debug, Deserialize)]
struct Links {
#[serde(default)]
next: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct CreatePageRequest<'a> {
#[serde(rename = "spaceId")]
pub space_id: &'a str,
pub status: &'static str,
pub title: &'a str,
#[serde(rename = "parentId", skip_serializing_if = "Option::is_none")]
pub parent_id: Option<&'a str>,
pub body: BodyRequest,
}
#[derive(Debug, Serialize)]
pub struct UpdatePageRequest<'a> {
pub id: &'a str,
pub status: &'static str,
pub title: &'a str,
pub version: VersionRequest,
pub body: BodyRequest,
}
#[derive(Debug, Serialize)]
pub struct BodyRequest {
pub representation: &'static str,
pub value: String,
}
#[derive(Debug, Serialize)]
pub struct VersionRequest {
pub number: u64,
}
impl BodyRequest {
pub fn from_adf(adf: &Value) -> Self {
Self {
representation: "atlas_doc_format",
value: serde_json::to_string(adf).unwrap_or_else(|_| "{}".to_string()),
}
}
}
pub async fn get(client: &Client, id: &str) -> Result<Page> {
let path = format!("wiki/api/v2/pages/{id}?body-format=atlas_doc_format");
client.request_json(Method::GET, &path).await
}
pub async fn list(
client: &Client,
space_id: &str,
parent_id: Option<&str>,
limit: usize,
) -> Result<Vec<PageSummary>> {
let mut results: Vec<PageSummary> = Vec::new();
let mut path = if let Some(parent) = parent_id {
format!("wiki/api/v2/pages/{parent}/children?limit=250")
} else {
format!("wiki/api/v2/spaces/{space_id}/pages?limit=250")
};
loop {
let page: PageList = client.request_json(Method::GET, &path).await?;
let got = page.results.len();
results.extend(page.results);
if limit > 0 && results.len() >= limit {
results.truncate(limit);
break;
}
match page.links.and_then(|l| l.next) {
Some(next) if got > 0 => {
path = next
.trim_start_matches('/')
.trim_start_matches("wiki/")
.to_string();
path = format!("wiki/{path}");
}
_ => break,
}
}
Ok(results)
}
pub async fn create(client: &Client, req: &CreatePageRequest<'_>) -> Result<Page> {
client.post_json("wiki/api/v2/pages", req).await
}
pub async fn update(client: &Client, id: &str, req: &UpdatePageRequest<'_>) -> Result<Page> {
let path = format!("wiki/api/v2/pages/{id}");
client.put_json(&path, req).await
}
pub async fn delete(client: &Client, id: &str) -> Result<()> {
let path = format!("wiki/api/v2/pages/{id}");
client.delete_no_content(&path).await
}