feat(server): synthetic root doc (#14794)

This commit is contained in:
DarkSky
2026-04-06 17:16:34 +08:00
committed by GitHub
parent 64149d909a
commit 193ec14ad3
35 changed files with 1784 additions and 409 deletions
+2 -1
View File
@@ -17,5 +17,6 @@ pub use read::{
parse_doc_from_binary, parse_doc_to_markdown, parse_page_doc, parse_workspace_doc,
};
pub use write::{
add_doc_to_root_doc, build_full_doc, update_doc, update_doc_properties, update_doc_title, update_root_doc_meta_title,
add_doc_to_root_doc, build_full_doc, build_public_root_doc, update_doc, update_doc_properties, update_doc_title,
update_root_doc_meta_title,
};
@@ -8,7 +8,7 @@ mod update;
pub use create::build_full_doc;
pub use doc_meta::{update_doc_title, update_root_doc_meta_title};
pub use doc_properties::update_doc_properties;
pub use root_doc::add_doc_to_root_doc;
pub use root_doc::{add_doc_to_root_doc, build_public_root_doc};
pub use update::update_doc;
use y_octo::{Any, Doc, Map, Value};
@@ -1,8 +1,19 @@
use std::collections::HashSet;
use y_octo::Array;
use super::*;
const DEFAULT_DOC_TITLE: &str = "Untitled";
const SIMPLE_PAGE_META_KEYS: &[&str] = &[
"id",
"title",
"createDate",
"updatedDate",
"trash",
"trashDate",
"headerImage",
];
fn any_to_value(doc: &Doc, any: Any) -> Result<Value, ParseError> {
match any {
@@ -104,3 +115,177 @@ pub fn add_doc_to_root_doc(root_doc_bin: Vec<u8>, doc_id: &str, title: Option<&s
// Encode only the changes (delta) since state_before
Ok(doc.encode_state_as_update_v1(&state_before)?)
}
fn insert_page_stub(doc: &Doc, pages: &mut Array, doc_id: &str, title: Option<&str>) -> Result<(), ParseError> {
let page_map = doc.create_map()?;
let idx = pages.len();
pages.insert(idx, Value::Map(page_map))?;
if let Some(mut inserted_page) = pages.get(idx).and_then(|v| v.to_map()) {
inserted_page.insert("id".to_string(), Any::String(doc_id.to_string()))?;
inserted_page.insert(
"title".to_string(),
Any::String(title.unwrap_or(DEFAULT_DOC_TITLE).to_string()),
)?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0);
inserted_page.insert("createDate".to_string(), Any::Float64((timestamp as f64).into()))?;
let tags = doc.create_array()?;
inserted_page.insert("tags".to_string(), Value::Array(tags))?;
}
Ok(())
}
fn insert_simple_array(doc: &Doc, values: &[Any]) -> Result<Value, ParseError> {
let mut array = doc.create_array()?;
for value in values {
match value {
Any::Array(_) | Any::Object(_) => continue,
_ => array.push(Value::Any(value.clone()))?,
}
}
Ok(Value::Array(array))
}
fn insert_page_from_any(doc: &Doc, pages: &mut Array, page: Any) -> Result<Option<String>, ParseError> {
let Any::Object(page) = page else {
return Ok(None);
};
let Some(page_id) = page.get("id").and_then(|value| match value {
Any::String(value) => Some(value.clone()),
_ => None,
}) else {
return Ok(None);
};
let page_map = doc.create_map()?;
let idx = pages.len();
pages.insert(idx, Value::Map(page_map))?;
let Some(mut inserted_page) = pages.get(idx).and_then(|v| v.to_map()) else {
return Ok(None);
};
for key in SIMPLE_PAGE_META_KEYS {
let Some(value) = page.get(*key) else {
continue;
};
match value {
Any::Array(values) => {
inserted_page.insert((*key).to_string(), insert_simple_array(doc, values)?)?;
}
Any::Object(_) => continue,
_ => {
inserted_page.insert((*key).to_string(), Value::Any(value.clone()))?;
}
}
}
if let Some(Any::Array(tags)) = page.get("tags") {
inserted_page.insert("tags".to_string(), insert_simple_array(doc, tags)?)?;
}
Ok(Some(page_id))
}
pub fn build_public_root_doc(root_doc_bin: &[u8], doc_metas: &[(&str, Option<&str>)]) -> Result<Vec<u8>, ParseError> {
let source = load_doc_or_new(root_doc_bin)?;
let public_doc_ids = doc_metas
.iter()
.map(|(doc_id, _title)| (*doc_id).to_string())
.collect::<HashSet<_>>();
let doc = Doc::default();
let mut meta = doc.get_or_create_map("meta")?;
let mut pages = ensure_pages_array(&doc, &mut meta)?;
let mut copied = HashSet::new();
if let Ok(source_meta) = source.get_map("meta") {
let source_pages_value = source_meta.get("pages");
if let Some(source_pages) = source_pages_value.as_ref().and_then(|value| value.to_array()) {
for page_val in source_pages.iter() {
let Some(page) = page_val.to_map() else {
continue;
};
let Some(page_id) = get_string(&page, "id") else {
continue;
};
if !public_doc_ids.contains(&page_id) {
continue;
}
let page_object = Any::Object(
page
.iter()
.filter_map(|(key, value)| value.to_any().map(|any| (key.to_string(), any)))
.collect(),
);
if let Some(inserted_page_id) = insert_page_from_any(&doc, &mut pages, page_object)? {
copied.insert(inserted_page_id);
}
}
} else if let Some(Any::Array(entries)) = source_pages_value.and_then(|value| value.to_any()) {
for entry in entries {
let Any::Object(page) = entry.clone() else {
continue;
};
let Some(Any::String(page_id)) = page.get("id") else {
continue;
};
if !public_doc_ids.contains(page_id) {
continue;
}
if let Some(inserted_page_id) = insert_page_from_any(&doc, &mut pages, entry)? {
copied.insert(inserted_page_id);
}
}
}
}
for (doc_id, title) in doc_metas {
if copied.contains(*doc_id) {
continue;
}
insert_page_stub(&doc, &mut pages, doc_id, *title)?;
}
Ok(doc.encode_update_v1()?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::doc_parser::get_doc_ids_from_binary;
#[test]
fn test_build_public_root_doc_filters_private_pages() {
let root = add_doc_to_root_doc(Vec::new(), "public-doc", Some("Public")).expect("create public entry");
let update = add_doc_to_root_doc(root.clone(), "private-doc", Some("Private")).expect("create private entry");
let mut merged = load_doc_or_new(&root).expect("load root");
merged
.apply_update_from_binary_v1(&update)
.expect("apply second update");
let merged_bin = merged.encode_update_v1().expect("encode merged");
let public_root = build_public_root_doc(
&merged_bin,
&[("public-doc", Some("Public")), ("missing-public-doc", Some("Fallback"))],
)
.expect("build public root");
let doc_ids = get_doc_ids_from_binary(public_root, false).expect("read public root doc ids");
assert_eq!(
doc_ids,
vec!["public-doc".to_string(), "missing-public-doc".to_string()]
);
}
}