Jelajahi Sumber

feat: public the json to document data pb api (#2694)

* feat: public the json to document data pb api

* test: add test for convert_data_to_document_internal

* chore: apply review suggestion

* chore: update folder path
Lucas.Xu 1 tahun lalu
induk
melakukan
561d0f0808

+ 2 - 2
frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart

@@ -54,11 +54,11 @@ AppFlowyEnv getAppFlowyEnv() {
 Future<Directory> appFlowyDocumentDirectory() async {
   switch (integrationEnv()) {
     case IntegrationMode.develop:
-      Directory documentsDir = await getApplicationDocumentsDirectory()
+      Directory documentsDir = await getApplicationSupportDirectory()
         ..create();
       return Directory(path.join(documentsDir.path, 'data_dev')).create();
     case IntegrationMode.release:
-      Directory documentsDir = await getApplicationDocumentsDirectory();
+      Directory documentsDir = await getApplicationSupportDirectory();
       return Directory(path.join(documentsDir.path, 'data')).create();
     case IntegrationMode.test:
       return Directory(path.join(Directory.current.path, '.sandbox'));

+ 5 - 0
frontend/rust-lib/flowy-core/assets/read_me.json

@@ -1,5 +1,10 @@
 {
   "type": "page",
+  "data": {
+    "delta": [
+      {"insert": ""}
+    ]
+  },
   "children": [
     {
       "type": "heading",

+ 4 - 5
frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs

@@ -10,7 +10,6 @@ use flowy_database2::entities::DatabaseLayoutPB;
 use flowy_database2::services::share::csv::CSVFormat;
 use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
 use flowy_database2::DatabaseManager2;
-use flowy_document2::document_data::default_document_data;
 use flowy_document2::entities::DocumentDataPB;
 use flowy_document2::manager::DocumentManager;
 use flowy_document2::parser::json::parser::JsonToDocumentParser;
@@ -105,7 +104,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
               let json_str = include_str!("../../assets/read_me.json");
               let document_pb = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
               manager
-                .create_document(view.parent_view.id.clone(), document_pb.into())
+                .create_document(view.parent_view.id.clone(), Some(document_pb.into()))
                 .unwrap();
               view
             })
@@ -152,7 +151,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
     let manager = self.0.clone();
     FutureResult::new(async move {
       let data = DocumentDataPB::try_from(Bytes::from(data))?;
-      manager.create_document(view_id, data.into())?;
+      manager.create_document(view_id, Some(data.into()))?;
       Ok(())
     })
   }
@@ -169,7 +168,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
     let view_id = view_id.to_string();
     let manager = self.0.clone();
     FutureResult::new(async move {
-      manager.create_document(view_id, default_document_data())?;
+      manager.create_document(view_id, None)?;
       Ok(())
     })
   }
@@ -184,7 +183,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
     let manager = self.0.clone();
     FutureResult::new(async move {
       let data = DocumentDataPB::try_from(Bytes::from(bytes))?;
-      manager.create_document(view_id, data.into())?;
+      manager.create_document(view_id, Some(data.into()))?;
       Ok(())
     })
   }

+ 2 - 0
frontend/rust-lib/flowy-document2/src/document_block_keys.rs

@@ -0,0 +1,2 @@
+pub const PAGE: &str = "page";
+pub const PARAGRAPH_BLOCK_TYPE: &str = "paragraph";

+ 6 - 3
frontend/rust-lib/flowy-document2/src/document_data.rs

@@ -3,7 +3,10 @@ use std::{collections::HashMap, vec};
 use collab_document::blocks::{Block, DocumentData, DocumentMeta};
 use nanoid::nanoid;
 
-use crate::entities::{BlockPB, ChildrenPB, DocumentDataPB, MetaPB};
+use crate::{
+  document_block_keys::{PAGE, PARAGRAPH_BLOCK_TYPE},
+  entities::{BlockPB, ChildrenPB, DocumentDataPB, MetaPB},
+};
 
 impl From<DocumentData> for DocumentDataPB {
   fn from(data: DocumentData) -> Self {
@@ -58,8 +61,8 @@ impl From<DocumentDataPB> for DocumentData {
 /// The default document data.
 /// The default document data is a document with a page block and a text block.
 pub fn default_document_data() -> DocumentData {
-  let page_type = "page".to_string();
-  let text_type = "text".to_string();
+  let page_type = PAGE.to_string();
+  let text_type = PARAGRAPH_BLOCK_TYPE.to_string();
 
   let mut blocks: HashMap<String, Block> = HashMap::new();
   let mut meta: HashMap<String, Vec<String>> = HashMap::new();

+ 33 - 0
frontend/rust-lib/flowy-document2/src/entities.rs

@@ -203,3 +203,36 @@ pub struct ExportDataPB {
   #[pb(index = 2)]
   pub export_type: ExportType,
 }
+#[derive(PartialEq, Eq, Debug, ProtoBuf_Enum, Clone)]
+pub enum ConvertType {
+  Json = 0,
+}
+
+impl Default for ConvertType {
+  fn default() -> Self {
+    ConvertType::Json
+  }
+}
+
+impl From<i32> for ConvertType {
+  fn from(val: i32) -> Self {
+    match val {
+      0 => ConvertType::Json,
+      _ => {
+        tracing::error!("Invalid export type: {}", val);
+        ConvertType::Json
+      },
+    }
+  }
+}
+
+/// for the json type
+/// the data is the json string
+#[derive(Default, ProtoBuf, Debug)]
+pub struct ConvertDataPayloadPB {
+  #[pb(index = 1)]
+  pub convert_type: ConvertType,
+
+  #[pb(index = 2)]
+  pub data: Vec<u8>,
+}

+ 28 - 8
frontend/rust-lib/flowy-document2/src/event_handler.rs

@@ -8,20 +8,20 @@ use std::sync::Arc;
 
 use collab_document::blocks::{
   json_str_to_hashmap, Block, BlockAction, BlockActionPayload, BlockActionType, BlockEvent,
-  BlockEventPayload, DeltaType, DocumentData,
+  BlockEventPayload, DeltaType,
 };
 
 use flowy_error::{FlowyError, FlowyResult};
 use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
 
-use crate::document_data::default_document_data;
 use crate::{
   entities::{
     ApplyActionPayloadPB, BlockActionPB, BlockActionPayloadPB, BlockActionTypePB, BlockEventPB,
-    BlockEventPayloadPB, BlockPB, CloseDocumentPayloadPB, CreateDocumentPayloadPB, DeltaTypePB,
-    DocEventPB, DocumentDataPB, OpenDocumentPayloadPB,
+    BlockEventPayloadPB, BlockPB, CloseDocumentPayloadPB, ConvertDataPayloadPB, ConvertType,
+    CreateDocumentPayloadPB, DeltaTypePB, DocEventPB, DocumentDataPB, OpenDocumentPayloadPB,
   },
   manager::DocumentManager,
+  parser::json::parser::JsonToDocumentParser,
 };
 
 // Handler for creating a new document
@@ -30,10 +30,7 @@ pub(crate) async fn create_document_handler(
   manager: AFPluginState<Arc<DocumentManager>>,
 ) -> FlowyResult<()> {
   let data = data.into_inner();
-  let initial_data = data
-    .initial_data
-    .map(|data| DocumentData::from(data))
-    .unwrap_or_else(default_document_data);
+  let initial_data = data.initial_data.map(|data| data.into());
   manager.create_document(data.document_id, initial_data)?;
   Ok(())
 }
@@ -83,6 +80,29 @@ pub(crate) async fn apply_action_handler(
   Ok(())
 }
 
+pub(crate) async fn convert_data_to_document(
+  data: AFPluginData<ConvertDataPayloadPB>,
+  _manager: AFPluginState<Arc<DocumentManager>>,
+) -> DataResult<DocumentDataPB, FlowyError> {
+  let payload = data.into_inner();
+  let document = convert_data_to_document_internal(payload)?;
+  data_result_ok(document)
+}
+
+pub fn convert_data_to_document_internal(
+  payload: ConvertDataPayloadPB,
+) -> Result<DocumentDataPB, FlowyError> {
+  let convert_type = payload.convert_type;
+  let data = payload.data;
+  match convert_type {
+    ConvertType::Json => {
+      let json_str = String::from_utf8(data).map_err(|_| FlowyError::invalid_data())?;
+      let document = JsonToDocumentParser::json_str_to_document(&json_str)?;
+      Ok(document)
+    },
+  }
+}
+
 impl From<BlockActionPB> for BlockAction {
   fn from(pb: BlockActionPB) -> Self {
     Self {

+ 9 - 2
frontend/rust-lib/flowy-document2/src/event_map.rs

@@ -6,8 +6,8 @@ use lib_dispatch::prelude::AFPlugin;
 
 use crate::{
   event_handler::{
-    apply_action_handler, close_document_handler, create_document_handler,
-    get_document_data_handler, open_document_handler,
+    apply_action_handler, close_document_handler, convert_data_to_document,
+    create_document_handler, get_document_data_handler, open_document_handler,
   },
   manager::DocumentManager,
 };
@@ -22,6 +22,10 @@ pub fn init(document_manager: Arc<DocumentManager>) -> AFPlugin {
   plugin = plugin.event(DocumentEvent::CloseDocument, close_document_handler);
   plugin = plugin.event(DocumentEvent::ApplyAction, apply_action_handler);
   plugin = plugin.event(DocumentEvent::GetDocumentData, get_document_data_handler);
+  plugin = plugin.event(
+    DocumentEvent::ConvertDataToDocument,
+    convert_data_to_document,
+  );
 
   plugin
 }
@@ -43,4 +47,7 @@ pub enum DocumentEvent {
 
   #[event(input = "GetDocumentDataPayloadPB")]
   GetDocumentData = 4,
+
+  #[event(input = "ConvertDataPayloadPB", output = "DocumentDataPB")]
+  ConvertDataToDocument = 5,
 }

+ 2 - 1
frontend/rust-lib/flowy-document2/src/lib.rs

@@ -1,10 +1,11 @@
 pub mod document;
+pub mod document_block_keys;
 pub mod document_data;
 pub mod entities;
+pub mod event_handler;
 pub mod event_map;
 pub mod manager;
 pub mod parser;
 pub mod protobuf;
 
-mod event_handler;
 mod notification;

+ 11 - 1
frontend/rust-lib/flowy-document2/src/manager.rs

@@ -9,6 +9,7 @@ use flowy_error::{FlowyError, FlowyResult};
 
 use crate::{
   document::Document,
+  document_data::default_document_data,
   entities::DocEventPB,
   notification::{send_notification, DocumentNotification},
 };
@@ -34,11 +35,20 @@ impl DocumentManager {
     }
   }
 
-  pub fn create_document(&self, doc_id: String, data: DocumentData) -> FlowyResult<Arc<Document>> {
+  /// Create a new document.
+  ///
+  /// if the document already exists, return the existing document.
+  /// if the data is None, will create a document with default data.
+  pub fn create_document(
+    &self,
+    doc_id: String,
+    data: Option<DocumentData>,
+  ) -> FlowyResult<Arc<Document>> {
     tracing::debug!("create a document: {:?}", &doc_id);
     let uid = self.user.user_id()?;
     let db = self.user.collab_db()?;
     let collab = self.collab_builder.build(uid, &doc_id, "document", db);
+    let data = data.unwrap_or_else(|| default_document_data());
     let document = Arc::new(Document::create_with_data(collab, data)?);
     Ok(document)
   }

+ 3 - 2
frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs

@@ -2,6 +2,7 @@ use std::{collections::HashMap, sync::Arc, vec};
 
 use crate::document::util::default_collab_builder;
 use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
+use flowy_document2::document_block_keys::PARAGRAPH_BLOCK_TYPE;
 use flowy_document2::document_data::default_document_data;
 use flowy_document2::{document::Document, manager::DocumentManager};
 use nanoid::nanoid;
@@ -16,7 +17,7 @@ fn document_apply_insert_block_with_empty_parent_id() {
   let text_block_id = nanoid!(10);
   let text_block = Block {
     id: text_block_id.clone(),
-    ty: "text".to_string(),
+    ty: PARAGRAPH_BLOCK_TYPE.to_string(),
     parent: "".to_string(),
     children: nanoid!(10),
     external_id: None,
@@ -47,7 +48,7 @@ fn create_and_open_empty_document() -> (DocumentManager, Arc<Document>, String)
 
   // create a document
   _ = manager
-    .create_document(doc_id.clone(), data.clone())
+    .create_document(doc_id.clone(), Some(data.clone()))
     .unwrap();
 
   let document = manager.open_document(doc_id).unwrap();

+ 8 - 7
frontend/rust-lib/flowy-document2/tests/document/document_test.rs

@@ -1,6 +1,7 @@
 use std::{collections::HashMap, sync::Arc, vec};
 
 use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
+use flowy_document2::document_block_keys::PARAGRAPH_BLOCK_TYPE;
 use nanoid::nanoid;
 use serde_json::{json, to_value, Value};
 
@@ -19,7 +20,7 @@ fn restore_document() {
   let doc_id: String = nanoid!(10);
   let data = default_document_data();
   let document_a = manager
-    .create_document(doc_id.clone(), data.clone())
+    .create_document(doc_id.clone(), Some(data.clone()))
     .unwrap();
   let data_a = document_a.lock().get_document().unwrap();
   assert_eq!(data_a, data);
@@ -36,7 +37,7 @@ fn restore_document() {
   assert_eq!(data_b, data);
 
   // restore
-  _ = manager.create_document(doc_id.clone(), data.clone());
+  _ = manager.create_document(doc_id.clone(), Some(data.clone()));
   // open a document
   let data_b = manager
     .open_document(doc_id.clone())
@@ -59,7 +60,7 @@ fn document_apply_insert_action() {
   let data = default_document_data();
 
   // create a document
-  _ = manager.create_document(doc_id.clone(), data.clone());
+  _ = manager.create_document(doc_id.clone(), Some(data.clone()));
 
   // open a document
   let document = manager.open_document(doc_id.clone()).unwrap();
@@ -68,7 +69,7 @@ fn document_apply_insert_action() {
   // insert a text block
   let text_block = Block {
     id: nanoid!(10),
-    ty: "text".to_string(),
+    ty: PARAGRAPH_BLOCK_TYPE.to_string(),
     parent: page_block.id,
     children: nanoid!(10),
     external_id: None,
@@ -110,7 +111,7 @@ fn document_apply_update_page_action() {
   let data = default_document_data();
 
   // create a document
-  _ = manager.create_document(doc_id.clone(), data.clone());
+  _ = manager.create_document(doc_id.clone(), Some(data.clone()));
 
   // open a document
   let document = manager.open_document(doc_id.clone()).unwrap();
@@ -152,7 +153,7 @@ fn document_apply_update_action() {
   let data = default_document_data();
 
   // create a document
-  _ = manager.create_document(doc_id.clone(), data.clone());
+  _ = manager.create_document(doc_id.clone(), Some(data.clone()));
 
   // open a document
   let document = manager.open_document(doc_id.clone()).unwrap();
@@ -162,7 +163,7 @@ fn document_apply_update_action() {
   let text_block_id = nanoid!(10);
   let text_block = Block {
     id: text_block_id.clone(),
-    ty: "text".to_string(),
+    ty: PARAGRAPH_BLOCK_TYPE.to_string(),
     parent: page_block.id,
     children: nanoid!(10),
     external_id: None,

+ 32 - 0
frontend/rust-lib/flowy-document2/tests/document/event_handler_test.rs

@@ -0,0 +1,32 @@
+use flowy_document2::{
+  entities::{ConvertDataPayloadPB, ConvertType},
+  event_handler::convert_data_to_document_internal,
+};
+
+#[test]
+fn convert_json_to_document() {
+  let json_str = r#"
+    {
+      "type": "page",
+      "children": [
+        {
+          "type": "paragraph1"
+        }
+      ]
+    }"#;
+  let payload = ConvertDataPayloadPB {
+    convert_type: ConvertType::Json,
+    data: json_str.as_bytes().to_vec(),
+  };
+  let document_data = convert_data_to_document_internal(payload).unwrap();
+
+  let page_id = document_data.page_id;
+  let blocks = document_data.blocks;
+  let children_map = document_data.meta.children_map;
+  let page_block = blocks.get(&page_id).unwrap();
+  let page_children = children_map.get(&page_block.children_id).unwrap();
+  assert_eq!(page_children.children.len(), 1);
+  let paragraph1 = blocks.get(page_children.children.get(0).unwrap()).unwrap();
+  assert_eq!(paragraph1.ty, "paragraph1");
+  assert_eq!(paragraph1.parent_id, page_block.id);
+}

+ 1 - 0
frontend/rust-lib/flowy-document2/tests/document/mod.rs

@@ -1,3 +1,4 @@
 mod document_insert_test;
 mod document_test;
+mod event_handler_test;
 mod util;