mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support publish view and unpublish views
This commit is contained in:
parent
4151c48180
commit
1c8913eb79
@ -1,3 +1,4 @@
|
|||||||
|
use collab::entity::EncodedCollab;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_folder::{FolderData, View};
|
use collab_folder::{FolderData, View};
|
||||||
@ -5,6 +6,7 @@ use flowy_folder::entities::icon::UpdateViewIconPayloadPB;
|
|||||||
use flowy_folder::event_map::FolderEvent;
|
use flowy_folder::event_map::FolderEvent;
|
||||||
use flowy_folder::event_map::FolderEvent::*;
|
use flowy_folder::event_map::FolderEvent::*;
|
||||||
use flowy_folder::{entities::*, ViewLayout};
|
use flowy_folder::{entities::*, ViewLayout};
|
||||||
|
use flowy_folder_pub::entities::PublishViewPayload;
|
||||||
use flowy_search::services::manager::{SearchHandler, SearchType};
|
use flowy_search::services::manager::{SearchHandler, SearchType};
|
||||||
use flowy_user::entities::{
|
use flowy_user::entities::{
|
||||||
AcceptWorkspaceInvitationPB, QueryWorkspacePB, RemoveWorkspaceMemberPB,
|
AcceptWorkspaceInvitationPB, QueryWorkspacePB, RemoveWorkspaceMemberPB,
|
||||||
@ -172,6 +174,24 @@ impl EventIntegrationTest {
|
|||||||
folder.get_folder_data(&workspace_id).clone().unwrap()
|
folder.get_folder_data(&workspace_id).clone().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_publish_payload(&self, view_id: &str) -> Vec<PublishViewPayload> {
|
||||||
|
let manager = self.folder_manager.clone();
|
||||||
|
let payload = manager.get_batch_publish_payload(view_id, None).await;
|
||||||
|
|
||||||
|
if payload.is_err() {
|
||||||
|
panic!("Get publish payload failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn encoded_collab_v1(&self, view_id: &str, layout: ViewLayout) -> EncodedCollab {
|
||||||
|
let manager = self.folder_manager.clone();
|
||||||
|
let handlers = manager.get_operation_handlers();
|
||||||
|
let handler = handlers.get(&layout).unwrap();
|
||||||
|
handler.encoded_collab_v1(view_id, layout).await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
|
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
|
||||||
EventBuilder::new(self.clone())
|
EventBuilder::new(self.clone())
|
||||||
.event(FolderEvent::ReadCurrentWorkspaceViews)
|
.event(FolderEvent::ReadCurrentWorkspaceViews)
|
||||||
|
@ -3,3 +3,4 @@ mod import_test;
|
|||||||
mod script;
|
mod script;
|
||||||
mod subscription_test;
|
mod subscription_test;
|
||||||
mod test;
|
mod test;
|
||||||
|
mod view_publish_test;
|
||||||
|
@ -0,0 +1,159 @@
|
|||||||
|
use collab_folder::ViewLayout;
|
||||||
|
use event_integration_test::EventIntegrationTest;
|
||||||
|
use flowy_folder::entities::{ViewLayoutPB, ViewPB};
|
||||||
|
use flowy_folder::publish_util::generate_publish_name;
|
||||||
|
use flowy_folder_pub::entities::{
|
||||||
|
PublishViewInfo, PublishViewMeta, PublishViewMetaData, PublishViewPayload,
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn mock_single_document_view_publish_payload(
|
||||||
|
test: &EventIntegrationTest,
|
||||||
|
view: &ViewPB,
|
||||||
|
publish_name: String,
|
||||||
|
) -> Vec<PublishViewPayload> {
|
||||||
|
let view_id = &view.id;
|
||||||
|
let layout: ViewLayout = view.layout.clone().into();
|
||||||
|
let view_encoded_collab = test.encoded_collab_v1(view_id, layout).await;
|
||||||
|
let publish_view_info = PublishViewInfo {
|
||||||
|
view_id: view_id.to_string(),
|
||||||
|
name: view.name.to_string(),
|
||||||
|
icon: None,
|
||||||
|
layout: ViewLayout::Document,
|
||||||
|
extra: None,
|
||||||
|
created_by: view.created_by,
|
||||||
|
last_edited_by: view.last_edited_by,
|
||||||
|
last_edited_time: view.last_edited,
|
||||||
|
created_at: view.create_time,
|
||||||
|
child_views: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![PublishViewPayload {
|
||||||
|
meta: PublishViewMeta {
|
||||||
|
metadata: PublishViewMetaData {
|
||||||
|
view: publish_view_info.clone(),
|
||||||
|
child_views: vec![],
|
||||||
|
ancestor_views: vec![publish_view_info],
|
||||||
|
},
|
||||||
|
view_id: view_id.to_string(),
|
||||||
|
publish_name,
|
||||||
|
},
|
||||||
|
data: Vec::from(view_encoded_collab.doc_state),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mock_nested_document_view_publish_payload(
|
||||||
|
test: &EventIntegrationTest,
|
||||||
|
view: &ViewPB,
|
||||||
|
publish_name: String,
|
||||||
|
) -> Vec<PublishViewPayload> {
|
||||||
|
let view_id = &view.id;
|
||||||
|
let layout: ViewLayout = view.layout.clone().into();
|
||||||
|
let view_encoded_collab = test.encoded_collab_v1(view_id, layout).await;
|
||||||
|
let publish_view_info = PublishViewInfo {
|
||||||
|
view_id: view_id.to_string(),
|
||||||
|
name: view.name.to_string(),
|
||||||
|
icon: None,
|
||||||
|
layout: ViewLayout::Document,
|
||||||
|
extra: None,
|
||||||
|
created_by: view.created_by,
|
||||||
|
last_edited_by: view.last_edited_by,
|
||||||
|
last_edited_time: view.last_edited,
|
||||||
|
created_at: view.create_time,
|
||||||
|
child_views: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let child_view_id = &view.child_views[0].id;
|
||||||
|
let child_view = test.get_view(child_view_id).await;
|
||||||
|
let child_layout: ViewLayout = child_view.layout.clone().into();
|
||||||
|
let child_view_encoded_collab = test.encoded_collab_v1(child_view_id, child_layout).await;
|
||||||
|
let child_publish_view_info = PublishViewInfo {
|
||||||
|
view_id: child_view_id.to_string(),
|
||||||
|
name: child_view.name.to_string(),
|
||||||
|
icon: None,
|
||||||
|
layout: ViewLayout::Document,
|
||||||
|
extra: None,
|
||||||
|
created_by: child_view.created_by,
|
||||||
|
last_edited_by: child_view.last_edited_by,
|
||||||
|
last_edited_time: child_view.last_edited,
|
||||||
|
created_at: child_view.create_time,
|
||||||
|
child_views: None,
|
||||||
|
};
|
||||||
|
let child_publish_name = generate_publish_name(&child_view.id, &child_view.name);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
PublishViewPayload {
|
||||||
|
meta: PublishViewMeta {
|
||||||
|
metadata: PublishViewMetaData {
|
||||||
|
view: publish_view_info.clone(),
|
||||||
|
child_views: vec![child_publish_view_info.clone()],
|
||||||
|
ancestor_views: vec![publish_view_info.clone()],
|
||||||
|
},
|
||||||
|
view_id: view_id.to_string(),
|
||||||
|
publish_name,
|
||||||
|
},
|
||||||
|
data: Vec::from(view_encoded_collab.doc_state),
|
||||||
|
},
|
||||||
|
PublishViewPayload {
|
||||||
|
meta: PublishViewMeta {
|
||||||
|
metadata: PublishViewMetaData {
|
||||||
|
view: child_publish_view_info.clone(),
|
||||||
|
child_views: vec![],
|
||||||
|
ancestor_views: vec![publish_view_info.clone(), child_publish_view_info.clone()],
|
||||||
|
},
|
||||||
|
view_id: child_view_id.to_string(),
|
||||||
|
publish_name: child_publish_name,
|
||||||
|
},
|
||||||
|
data: Vec::from(child_view_encoded_collab.doc_state),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_single_document(test: &EventIntegrationTest, view_id: &str, name: &str) {
|
||||||
|
test
|
||||||
|
.create_orphan_view(name, view_id, ViewLayoutPB::Document)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_nested_document(test: &EventIntegrationTest, view_id: &str, name: &str) {
|
||||||
|
create_single_document(test, view_id, name).await;
|
||||||
|
let child_name = "Child View";
|
||||||
|
test.create_view(view_id, child_name.to_string()).await;
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn single_document_get_publish_view_payload_test() {
|
||||||
|
let test = EventIntegrationTest::new_anon().await;
|
||||||
|
let view_id = "20240521";
|
||||||
|
let name = "Orphan View";
|
||||||
|
create_single_document(&test, view_id, name).await;
|
||||||
|
let view = test.get_view(view_id).await;
|
||||||
|
let payload = test.get_publish_payload(view_id).await;
|
||||||
|
|
||||||
|
let expect_payload = mock_single_document_view_publish_payload(
|
||||||
|
&test,
|
||||||
|
&view,
|
||||||
|
format!("{}-{}", "Orphan_View", view_id),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(payload, expect_payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn nested_document_get_publish_view_payload_test() {
|
||||||
|
let test = EventIntegrationTest::new_anon().await;
|
||||||
|
let name = "Orphan View";
|
||||||
|
let view_id = "20240521";
|
||||||
|
create_nested_document(&test, view_id, name).await;
|
||||||
|
let view = test.get_view(view_id).await;
|
||||||
|
let payload = test.get_publish_payload(view_id).await;
|
||||||
|
|
||||||
|
let expect_payload = mock_nested_document_view_publish_payload(
|
||||||
|
&test,
|
||||||
|
&view,
|
||||||
|
format!("{}-{}", "Orphan_View", view_id),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(payload.len(), 2);
|
||||||
|
assert_eq!(payload, expect_payload);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use collab::entity::EncodedCollab;
|
||||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||||
use collab_integrate::CollabKVDB;
|
use collab_integrate::CollabKVDB;
|
||||||
use flowy_chat::chat_manager::ChatManager;
|
use flowy_chat::chat_manager::ChatManager;
|
||||||
@ -200,6 +201,21 @@ impl FolderOperationHandler for DocumentFolderOperation {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encoded_collab_v1(
|
||||||
|
&self,
|
||||||
|
view_id: &str,
|
||||||
|
layout: ViewLayout,
|
||||||
|
) -> FutureResult<EncodedCollab, FlowyError> {
|
||||||
|
debug_assert_eq!(layout, ViewLayout::Document);
|
||||||
|
let view_id = view_id.to_string();
|
||||||
|
let manager = self.0.clone();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
let encoded_collab = manager.encode_collab(&view_id).await?;
|
||||||
|
|
||||||
|
Ok(encoded_collab)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a view with built-in data.
|
/// Create a view with built-in data.
|
||||||
fn create_built_in_view(
|
fn create_built_in_view(
|
||||||
&self,
|
&self,
|
||||||
@ -287,6 +303,15 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encoded_collab_v1(
|
||||||
|
&self,
|
||||||
|
_view_id: &str,
|
||||||
|
_layout: ViewLayout,
|
||||||
|
) -> FutureResult<EncodedCollab, FlowyError> {
|
||||||
|
// Database view doesn't support collab
|
||||||
|
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||||
|
}
|
||||||
|
|
||||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
fn duplicate_view(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||||
let database_manager = self.0.clone();
|
let database_manager = self.0.clone();
|
||||||
let view_id = view_id.to_owned();
|
let view_id = view_id.to_owned();
|
||||||
@ -547,4 +572,13 @@ impl FolderOperationHandler for ChatFolderOperation {
|
|||||||
) -> FutureResult<(), FlowyError> {
|
) -> FutureResult<(), FlowyError> {
|
||||||
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encoded_collab_v1(
|
||||||
|
&self,
|
||||||
|
_view_id: &str,
|
||||||
|
_layout: ViewLayout,
|
||||||
|
) -> FutureResult<EncodedCollab, FlowyError> {
|
||||||
|
// Chat view doesn't support collab
|
||||||
|
FutureResult::new(async move { Err(FlowyError::not_support()) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ use flowy_error::{FlowyError, FlowyResult};
|
|||||||
use flowy_folder_pub::cloud::{
|
use flowy_folder_pub::cloud::{
|
||||||
FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot, Workspace, WorkspaceRecord,
|
FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot, Workspace, WorkspaceRecord,
|
||||||
};
|
};
|
||||||
|
use flowy_folder_pub::entities::{PublishInfoResponse, PublishViewPayload};
|
||||||
use flowy_server_pub::af_cloud_config::AFCloudConfiguration;
|
use flowy_server_pub::af_cloud_config::AFCloudConfiguration;
|
||||||
use flowy_server_pub::supabase_config::SupabaseConfiguration;
|
use flowy_server_pub::supabase_config::SupabaseConfiguration;
|
||||||
use flowy_storage_pub::cloud::{ObjectIdentity, ObjectValue, StorageCloudService};
|
use flowy_storage_pub::cloud::{ObjectIdentity, ObjectValue, StorageCloudService};
|
||||||
@ -301,6 +302,65 @@ impl FolderCloudService for ServerProvider {
|
|||||||
.map(|provider| provider.folder_service().service_name())
|
.map(|provider| provider.folder_service().service_name())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn publish_view(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
payload: Vec<PublishViewPayload>,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let server = self.get_server();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
server?
|
||||||
|
.folder_service()
|
||||||
|
.publish_view(&workspace_id, payload)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpublish_views(&self, workspace_id: &str, view_ids: Vec<String>) -> FutureResult<(), Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let server = self.get_server();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
server?
|
||||||
|
.folder_service()
|
||||||
|
.unpublish_views(&workspace_id, view_ids)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_info(&self, view_id: &str) -> FutureResult<PublishInfoResponse, Error> {
|
||||||
|
let view_id = view_id.to_string();
|
||||||
|
let server = self.get_server();
|
||||||
|
FutureResult::new(async move { server?.folder_service().get_publish_info(&view_id).await })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_publish_namespace(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
new_namespace: &str,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let new_namespace = new_namespace.to_string();
|
||||||
|
let server = self.get_server();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
server?
|
||||||
|
.folder_service()
|
||||||
|
.set_publish_namespace(&workspace_id, &new_namespace)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_namespace(&self, workspace_id: &str) -> FutureResult<String, Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let server = self.get_server();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
server?
|
||||||
|
.folder_service()
|
||||||
|
.get_publish_namespace(&workspace_id)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseCloudService for ServerProvider {
|
impl DatabaseCloudService for ServerProvider {
|
||||||
|
@ -11,8 +11,8 @@ use collab_document::document_awareness::DocumentAwarenessState;
|
|||||||
use collab_document::document_awareness::DocumentAwarenessUser;
|
use collab_document::document_awareness::DocumentAwarenessUser;
|
||||||
use collab_document::document_data::default_document_data;
|
use collab_document::document_data::default_document_data;
|
||||||
use collab_entity::CollabType;
|
use collab_entity::CollabType;
|
||||||
use collab_plugins::CollabKVDB;
|
|
||||||
use collab_plugins::local_storage::kv::PersistenceError;
|
use collab_plugins::local_storage::kv::PersistenceError;
|
||||||
|
use collab_plugins::CollabKVDB;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use lib_infra::util::timestamp;
|
use lib_infra::util::timestamp;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
@ -85,7 +85,9 @@ impl DocumentManager {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let collab = collab.lock();
|
let collab = collab.lock();
|
||||||
collab.encode_collab_v1(|_| Ok::<(), PersistenceError>(())).map_err(internal_error)
|
collab
|
||||||
|
.encode_collab_v1(|_| Ok::<(), PersistenceError>(()))
|
||||||
|
.map_err(internal_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initialize(&self, _uid: i64) -> FlowyResult<()> {
|
pub async fn initialize(&self, _uid: i64) -> FlowyResult<()> {
|
||||||
|
@ -12,6 +12,7 @@ collab = { workspace = true }
|
|||||||
collab-entity = { workspace = true }
|
collab-entity = { workspace = true }
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
@ -3,6 +3,7 @@ use collab_entity::CollabType;
|
|||||||
pub use collab_folder::{Folder, FolderData, Workspace};
|
pub use collab_folder::{Folder, FolderData, Workspace};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::entities::{PublishInfoResponse, PublishViewPayload};
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
|
|
||||||
/// [FolderCloudService] represents the cloud service for folder.
|
/// [FolderCloudService] represents the cloud service for folder.
|
||||||
@ -44,6 +45,24 @@ pub trait FolderCloudService: Send + Sync + 'static {
|
|||||||
) -> FutureResult<(), Error>;
|
) -> FutureResult<(), Error>;
|
||||||
|
|
||||||
fn service_name(&self) -> String;
|
fn service_name(&self) -> String;
|
||||||
|
|
||||||
|
fn publish_view(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
payload: Vec<PublishViewPayload>,
|
||||||
|
) -> FutureResult<(), Error>;
|
||||||
|
|
||||||
|
fn unpublish_views(&self, workspace_id: &str, view_ids: Vec<String>) -> FutureResult<(), Error>;
|
||||||
|
|
||||||
|
fn get_publish_info(&self, view_id: &str) -> FutureResult<PublishInfoResponse, Error>;
|
||||||
|
|
||||||
|
fn set_publish_namespace(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
new_namespace: &str,
|
||||||
|
) -> FutureResult<(), Error>;
|
||||||
|
|
||||||
|
fn get_publish_namespace(&self, workspace_id: &str) -> FutureResult<String, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use crate::folder_builder::ParentChildViews;
|
use crate::folder_builder::ParentChildViews;
|
||||||
|
use collab_folder::{ViewIcon, ViewLayout};
|
||||||
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub enum ImportData {
|
pub enum ImportData {
|
||||||
@ -39,3 +41,44 @@ pub struct SearchData {
|
|||||||
/// The data that is stored in the search index row.
|
/// The data that is stored in the search index row.
|
||||||
pub data: String,
|
pub data: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct PublishViewInfo {
|
||||||
|
pub view_id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub icon: Option<ViewIcon>,
|
||||||
|
pub layout: ViewLayout,
|
||||||
|
pub extra: Option<String>,
|
||||||
|
pub created_by: Option<i64>,
|
||||||
|
pub last_edited_by: Option<i64>,
|
||||||
|
pub last_edited_time: i64,
|
||||||
|
pub created_at: i64,
|
||||||
|
pub child_views: Option<Vec<PublishViewInfo>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct PublishViewMetaData {
|
||||||
|
pub view: PublishViewInfo,
|
||||||
|
pub child_views: Vec<PublishViewInfo>,
|
||||||
|
pub ancestor_views: Vec<PublishViewInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct PublishViewMeta {
|
||||||
|
pub metadata: PublishViewMetaData,
|
||||||
|
pub view_id: String,
|
||||||
|
pub publish_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct PublishViewPayload {
|
||||||
|
pub meta: PublishViewMeta,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct PublishInfoResponse {
|
||||||
|
pub view_id: String,
|
||||||
|
pub publish_name: String,
|
||||||
|
pub namespace: Option<String>,
|
||||||
|
}
|
||||||
|
@ -39,6 +39,9 @@ serde = { workspace = true, features = ["derive"] }
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
validator.workspace = true
|
validator.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
regex = "1.9.5"
|
||||||
|
futures = "0.3.30"
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
flowy-codegen.workspace = true
|
flowy-codegen.workspace = true
|
||||||
|
@ -39,7 +39,7 @@ pub struct ViewIconPB {
|
|||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<ViewIconPB> for ViewIcon {
|
impl From<ViewIconPB> for ViewIcon {
|
||||||
fn from(rev: ViewIconPB) -> Self {
|
fn from(rev: ViewIconPB) -> Self {
|
||||||
ViewIcon {
|
ViewIcon {
|
||||||
ty: rev.ty.into(),
|
ty: rev.ty.into(),
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
pub mod icon;
|
pub mod icon;
|
||||||
mod import;
|
mod import;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
pub mod publish;
|
||||||
pub mod trash;
|
pub mod trash;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
pub use import::*;
|
pub use import::*;
|
||||||
|
pub use publish::*;
|
||||||
pub use trash::*;
|
pub use trash::*;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
pub use workspace::*;
|
pub use workspace::*;
|
||||||
|
48
frontend/rust-lib/flowy-folder/src/entities/publish.rs
Normal file
48
frontend/rust-lib/flowy-folder/src/entities/publish.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use flowy_derive::ProtoBuf;
|
||||||
|
use flowy_folder_pub::entities::PublishInfoResponse;
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf)]
|
||||||
|
pub struct PublishViewParamsPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_id: String,
|
||||||
|
#[pb(index = 2, one_of)]
|
||||||
|
pub publish_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf)]
|
||||||
|
pub struct UnpublishViewsPayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_ids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf)]
|
||||||
|
pub struct PublishInfoResponsePB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_id: String,
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub publish_name: String,
|
||||||
|
#[pb(index = 3, one_of)]
|
||||||
|
pub namespace: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PublishInfoResponse> for PublishInfoResponsePB {
|
||||||
|
fn from(info: PublishInfoResponse) -> Self {
|
||||||
|
Self {
|
||||||
|
view_id: info.view_id,
|
||||||
|
publish_name: info.publish_name,
|
||||||
|
namespace: info.namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf)]
|
||||||
|
pub struct SetPublishNamespacePayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub new_namespace: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf)]
|
||||||
|
pub struct PublishNamespacePB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub namespace: String,
|
||||||
|
}
|
@ -394,3 +394,58 @@ pub(crate) async fn update_view_visibility_status_handler(
|
|||||||
folder.set_views_visibility(params.view_ids, params.is_public);
|
folder.set_views_visibility(params.view_ids, params.is_public);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
||||||
|
pub(crate) async fn publish_view_handler(
|
||||||
|
data: AFPluginData<PublishViewParamsPB>,
|
||||||
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
|
) -> Result<(), FlowyError> {
|
||||||
|
let folder = upgrade_folder(folder)?;
|
||||||
|
let params = data.into_inner();
|
||||||
|
folder
|
||||||
|
.publish_view(params.view_id.as_str(), params.publish_name)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
||||||
|
pub(crate) async fn unpublish_views_handler(
|
||||||
|
data: AFPluginData<UnpublishViewsPayloadPB>,
|
||||||
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
|
) -> Result<(), FlowyError> {
|
||||||
|
let folder = upgrade_folder(folder)?;
|
||||||
|
let params = data.into_inner();
|
||||||
|
folder.unpublish_views(params.view_ids).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
||||||
|
pub(crate) async fn get_publish_info_handler(
|
||||||
|
data: AFPluginData<ViewIdPB>,
|
||||||
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
|
) -> DataResult<PublishInfoResponsePB, FlowyError> {
|
||||||
|
let folder = upgrade_folder(folder)?;
|
||||||
|
let view_id = data.into_inner().value;
|
||||||
|
let info = folder.get_publish_info(&view_id).await?;
|
||||||
|
data_result_ok(PublishInfoResponsePB::from(info))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
||||||
|
pub(crate) async fn set_publish_namespace_handler(
|
||||||
|
data: AFPluginData<SetPublishNamespacePayloadPB>,
|
||||||
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
|
) -> Result<(), FlowyError> {
|
||||||
|
let folder = upgrade_folder(folder)?;
|
||||||
|
let namespace = data.into_inner().new_namespace;
|
||||||
|
folder.set_publish_namespace(namespace).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(folder), err)]
|
||||||
|
pub(crate) async fn get_publish_namespace_handler(
|
||||||
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
|
) -> DataResult<PublishNamespacePB, FlowyError> {
|
||||||
|
let folder = upgrade_folder(folder)?;
|
||||||
|
let namespace = folder.get_publish_namespace().await?;
|
||||||
|
data_result_ok(PublishNamespacePB { namespace })
|
||||||
|
}
|
||||||
|
@ -42,6 +42,11 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
|
|||||||
.event(FolderEvent::ReadCurrentWorkspaceViews, get_current_workspace_views_handler)
|
.event(FolderEvent::ReadCurrentWorkspaceViews, get_current_workspace_views_handler)
|
||||||
.event(FolderEvent::UpdateViewVisibilityStatus, update_view_visibility_status_handler)
|
.event(FolderEvent::UpdateViewVisibilityStatus, update_view_visibility_status_handler)
|
||||||
.event(FolderEvent::GetViewAncestors, get_view_ancestors_handler)
|
.event(FolderEvent::GetViewAncestors, get_view_ancestors_handler)
|
||||||
|
.event(FolderEvent::PublishView, publish_view_handler)
|
||||||
|
.event(FolderEvent::GetPublishInfo, get_publish_info_handler)
|
||||||
|
.event(FolderEvent::UnpublishViews, unpublish_views_handler)
|
||||||
|
.event(FolderEvent::SetPublishNamespace, set_publish_namespace_handler)
|
||||||
|
.event(FolderEvent::GetPublishNamespace, get_publish_namespace_handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||||
@ -176,4 +181,19 @@ pub enum FolderEvent {
|
|||||||
/// Return the ancestors of the view
|
/// Return the ancestors of the view
|
||||||
#[event(input = "ViewIdPB", output = "RepeatedViewPB")]
|
#[event(input = "ViewIdPB", output = "RepeatedViewPB")]
|
||||||
GetViewAncestors = 42,
|
GetViewAncestors = 42,
|
||||||
|
|
||||||
|
#[event(input = "PublishViewParamsPB")]
|
||||||
|
PublishView = 43,
|
||||||
|
|
||||||
|
#[event(input = "ViewIdPB", output = "PublishInfoResponsePB")]
|
||||||
|
GetPublishInfo = 44,
|
||||||
|
|
||||||
|
#[event(output = "PublishNamespacePB")]
|
||||||
|
GetPublishNamespace = 45,
|
||||||
|
|
||||||
|
#[event(input = "SetPublishNamespacePayloadPB")]
|
||||||
|
SetPublishNamespace = 46,
|
||||||
|
|
||||||
|
#[event(input = "UnpublishViewsPayloadPB")]
|
||||||
|
UnpublishViews = 47,
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ mod manager_observer;
|
|||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub mod manager_test_util;
|
pub mod manager_test_util;
|
||||||
|
|
||||||
|
pub mod publish_util;
|
||||||
pub mod share;
|
pub mod share;
|
||||||
#[cfg(feature = "test_helper")]
|
#[cfg(feature = "test_helper")]
|
||||||
mod test_helper;
|
mod test_helper;
|
||||||
|
@ -12,6 +12,7 @@ use crate::manager_observer::{
|
|||||||
use crate::notification::{
|
use crate::notification::{
|
||||||
send_notification, send_workspace_setting_notification, FolderNotification,
|
send_notification, send_workspace_setting_notification, FolderNotification,
|
||||||
};
|
};
|
||||||
|
use crate::publish_util::{generate_publish_name, view_pb_to_publish_view};
|
||||||
use crate::share::ImportParams;
|
use crate::share::ImportParams;
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
folder_not_init_error, insert_parent_child_views, workspace_data_not_sync_error,
|
folder_not_init_error, insert_parent_child_views, workspace_data_not_sync_error,
|
||||||
@ -28,9 +29,13 @@ use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabBuilderConfi
|
|||||||
use collab_integrate::CollabKVDB;
|
use collab_integrate::CollabKVDB;
|
||||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
use flowy_folder_pub::cloud::{gen_view_id, FolderCloudService};
|
use flowy_folder_pub::cloud::{gen_view_id, FolderCloudService};
|
||||||
|
use flowy_folder_pub::entities::{
|
||||||
|
PublishInfoResponse, PublishViewInfo, PublishViewMeta, PublishViewMetaData, PublishViewPayload,
|
||||||
|
};
|
||||||
use flowy_folder_pub::folder_builder::ParentChildViews;
|
use flowy_folder_pub::folder_builder::ParentChildViews;
|
||||||
use flowy_search_pub::entities::FolderIndexManager;
|
use flowy_search_pub::entities::FolderIndexManager;
|
||||||
use flowy_sqlite::kv::StorePreferences;
|
use flowy_sqlite::kv::StorePreferences;
|
||||||
|
use futures::future;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@ -936,6 +941,192 @@ impl FolderManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The view will be published to the web with the specified view id.
|
||||||
|
/// The [publish_name] is the [view name] + [view id] when currently published
|
||||||
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||||
|
pub async fn publish_view(&self, view_id: &str, publish_name: Option<String>) -> FlowyResult<()> {
|
||||||
|
let view = self
|
||||||
|
.with_folder(|| None, |folder| folder.views.get_view(view_id))
|
||||||
|
.ok_or_else(|| FlowyError::record_not_found().with_context("Can't find the view"))?;
|
||||||
|
|
||||||
|
let layout = view.layout.clone();
|
||||||
|
|
||||||
|
if layout != ViewLayout::Document {
|
||||||
|
return Err(FlowyError::new(
|
||||||
|
ErrorCode::NotSupportYet,
|
||||||
|
"Only document view can be published".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the view payload and its child views recursively
|
||||||
|
let payload = self
|
||||||
|
.get_batch_publish_payload(view_id, publish_name)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let workspace_id = self.user.workspace_id()?;
|
||||||
|
self
|
||||||
|
.cloud_service
|
||||||
|
.publish_view(workspace_id.as_str(), payload)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||||
|
pub async fn unpublish_views(&self, view_ids: Vec<String>) -> FlowyResult<()> {
|
||||||
|
let workspace_id = self.user.workspace_id()?;
|
||||||
|
self
|
||||||
|
.cloud_service
|
||||||
|
.unpublish_views(workspace_id.as_str(), view_ids)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||||
|
pub async fn get_publish_info(&self, view_id: &str) -> FlowyResult<PublishInfoResponse> {
|
||||||
|
let publish_info = self.cloud_service.get_publish_info(view_id).await?;
|
||||||
|
Ok(publish_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||||
|
pub async fn set_publish_namespace(&self, namespace: String) -> FlowyResult<()> {
|
||||||
|
let workspace_id = self.user.workspace_id()?;
|
||||||
|
self
|
||||||
|
.cloud_service
|
||||||
|
.set_publish_namespace(workspace_id.as_str(), namespace.as_str())
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||||
|
pub async fn get_publish_namespace(&self) -> FlowyResult<String> {
|
||||||
|
let workspace_id = self.user.workspace_id()?;
|
||||||
|
let namespace = self
|
||||||
|
.cloud_service
|
||||||
|
.get_publish_namespace(workspace_id.as_str())
|
||||||
|
.await?;
|
||||||
|
Ok(namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the publishing payload of the view with the given view id.
|
||||||
|
/// The publishing payload contains the view data and its child views(not recursively).
|
||||||
|
pub async fn get_batch_publish_payload(
|
||||||
|
&self,
|
||||||
|
view_id: &str,
|
||||||
|
publish_name: Option<String>,
|
||||||
|
) -> FlowyResult<Vec<PublishViewPayload>> {
|
||||||
|
let mut stack = vec![view_id.to_string()];
|
||||||
|
let mut payloads = Vec::new();
|
||||||
|
|
||||||
|
while let Some(current_view_id) = stack.pop() {
|
||||||
|
let view = match self.get_view_pb(¤t_view_id).await {
|
||||||
|
Ok(view) => view,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only document view can be published
|
||||||
|
let layout = if view.layout == ViewLayoutPB::Document {
|
||||||
|
ViewLayout::Document
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only support set the publish_name for the current view, not for the child views
|
||||||
|
let publish_name = if current_view_id == view_id {
|
||||||
|
publish_name.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let payload = self
|
||||||
|
.get_publish_payload(¤t_view_id, publish_name, layout)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Ok(payload) = payload {
|
||||||
|
payloads.push(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the child views to the stack
|
||||||
|
for child in &view.child_views {
|
||||||
|
stack.push(child.id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(payloads)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn build_publish_views(&self, view_id: &str) -> Option<PublishViewInfo> {
|
||||||
|
let view_pb = self.get_view_pb(view_id).await.ok()?;
|
||||||
|
|
||||||
|
let mut child_views_futures = vec![];
|
||||||
|
|
||||||
|
for child in &view_pb.child_views {
|
||||||
|
let future = self.build_publish_views(&child.id);
|
||||||
|
child_views_futures.push(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
let child_views = future::join_all(child_views_futures)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<PublishViewInfo>>();
|
||||||
|
|
||||||
|
let view_child_views = if child_views.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(child_views)
|
||||||
|
};
|
||||||
|
|
||||||
|
let view = view_pb_to_publish_view(&view_pb);
|
||||||
|
|
||||||
|
let view = PublishViewInfo {
|
||||||
|
child_views: view_child_views,
|
||||||
|
..view
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(view)
|
||||||
|
}
|
||||||
|
async fn get_publish_payload(
|
||||||
|
&self,
|
||||||
|
view_id: &str,
|
||||||
|
publish_name: Option<String>,
|
||||||
|
layout: ViewLayout,
|
||||||
|
) -> FlowyResult<PublishViewPayload> {
|
||||||
|
let handler = self.get_handler(&layout)?;
|
||||||
|
let encoded_collab = handler.encoded_collab_v1(view_id, layout).await?;
|
||||||
|
let view = self
|
||||||
|
.with_folder(|| None, |folder| folder.views.get_view(view_id))
|
||||||
|
.ok_or_else(|| FlowyError::record_not_found().with_context("Can't find the view"))?;
|
||||||
|
let publish_name = publish_name.unwrap_or_else(|| generate_publish_name(&view.id, &view.name));
|
||||||
|
|
||||||
|
let child_views = self
|
||||||
|
.build_publish_views(view_id)
|
||||||
|
.await
|
||||||
|
.map(|v| v.child_views.map_or(vec![], |c| c))
|
||||||
|
.map_or(vec![], |c| c);
|
||||||
|
|
||||||
|
let ancestor_views = self
|
||||||
|
.get_view_ancestors_pb(view_id)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(view_pb_to_publish_view)
|
||||||
|
.collect::<Vec<PublishViewInfo>>();
|
||||||
|
|
||||||
|
let view_pb = self.get_view_pb(view_id).await?;
|
||||||
|
let metadata = PublishViewMetaData {
|
||||||
|
view: view_pb_to_publish_view(&view_pb),
|
||||||
|
child_views,
|
||||||
|
ancestor_views,
|
||||||
|
};
|
||||||
|
let meta = PublishViewMeta {
|
||||||
|
view_id: view.id.clone(),
|
||||||
|
publish_name,
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = Vec::from(encoded_collab.doc_state);
|
||||||
|
Ok(PublishViewPayload { meta, data })
|
||||||
|
}
|
||||||
|
|
||||||
// Used by toggle_favorites to send notification to frontend, after the favorite status of view has been changed.It sends two distinct notifications: one to correctly update the concerned view's is_favorite status, and another to update the list of favorites that is to be displayed.
|
// Used by toggle_favorites to send notification to frontend, after the favorite status of view has been changed.It sends two distinct notifications: one to correctly update the concerned view's is_favorite status, and another to update the list of favorites that is to be displayed.
|
||||||
async fn send_toggle_favorite_notification(&self, view_id: &str) {
|
async fn send_toggle_favorite_notification(&self, view_id: &str) {
|
||||||
if let Ok(view) = self.get_view_pb(view_id).await {
|
if let Ok(view) = self.get_view_pb(view_id).await {
|
||||||
|
33
frontend/rust-lib/flowy-folder/src/publish_util.rs
Normal file
33
frontend/rust-lib/flowy-folder/src/publish_util.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use crate::entities::ViewPB;
|
||||||
|
use flowy_folder_pub::entities::PublishViewInfo;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
fn replace_invalid_url_chars(input: &str) -> String {
|
||||||
|
let re = Regex::new(r"[^\w-]").unwrap();
|
||||||
|
|
||||||
|
let replaced = re.replace_all(input, "_").to_string();
|
||||||
|
if replaced.len() > 20 {
|
||||||
|
replaced[..20].to_string()
|
||||||
|
} else {
|
||||||
|
replaced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn generate_publish_name(id: &str, name: &str) -> String {
|
||||||
|
let name = replace_invalid_url_chars(name);
|
||||||
|
format!("{}-{}", name, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_pb_to_publish_view(view: &ViewPB) -> PublishViewInfo {
|
||||||
|
PublishViewInfo {
|
||||||
|
view_id: view.id.clone(),
|
||||||
|
name: view.name.clone(),
|
||||||
|
layout: view.layout.clone().into(),
|
||||||
|
icon: view.icon.clone().map(|icon| icon.into()),
|
||||||
|
child_views: None,
|
||||||
|
extra: view.extra.clone(),
|
||||||
|
created_by: view.created_by,
|
||||||
|
last_edited_by: view.last_edited_by,
|
||||||
|
last_edited_time: view.last_edited,
|
||||||
|
created_at: view.create_time,
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use collab::entity::EncodedCollab;
|
||||||
|
|
||||||
pub use collab_folder::View;
|
pub use collab_folder::View;
|
||||||
use collab_folder::ViewLayout;
|
use collab_folder::ViewLayout;
|
||||||
@ -45,6 +46,12 @@ pub trait FolderOperationHandler {
|
|||||||
/// Returns the [ViewData] that can be used to create the same view.
|
/// Returns the [ViewData] that can be used to create the same view.
|
||||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<ViewData, FlowyError>;
|
fn duplicate_view(&self, view_id: &str) -> FutureResult<ViewData, FlowyError>;
|
||||||
|
|
||||||
|
fn encoded_collab_v1(
|
||||||
|
&self,
|
||||||
|
view_id: &str,
|
||||||
|
layout: ViewLayout,
|
||||||
|
) -> FutureResult<EncodedCollab, FlowyError>;
|
||||||
|
|
||||||
/// Create a view with the data.
|
/// Create a view with the data.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use client_api::entity::{
|
use client_api::entity::{
|
||||||
workspace_dto::CreateWorkspaceParam, CollabParams, QueryCollab, QueryCollabParams,
|
workspace_dto::CreateWorkspaceParam, CollabParams, PublishCollabItem, PublishCollabMetadata,
|
||||||
|
QueryCollab, QueryCollabParams,
|
||||||
};
|
};
|
||||||
use collab::core::collab::DataSource;
|
use collab::core::collab::DataSource;
|
||||||
use collab::core::origin::CollabOrigin;
|
use collab::core::origin::CollabOrigin;
|
||||||
@ -8,12 +9,14 @@ use collab_entity::CollabType;
|
|||||||
use collab_folder::RepeatedViewIdentifier;
|
use collab_folder::RepeatedViewIdentifier;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::{ErrorCode, FlowyError};
|
||||||
use flowy_folder_pub::cloud::{
|
use flowy_folder_pub::cloud::{
|
||||||
Folder, FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot, Workspace,
|
Folder, FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot, Workspace,
|
||||||
WorkspaceRecord,
|
WorkspaceRecord,
|
||||||
};
|
};
|
||||||
|
use flowy_folder_pub::entities::{PublishInfoResponse, PublishViewPayload};
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
|
|
||||||
use crate::af_cloud::define::ServerUser;
|
use crate::af_cloud::define::ServerUser;
|
||||||
@ -180,4 +183,95 @@ where
|
|||||||
fn service_name(&self) -> String {
|
fn service_name(&self) -> String {
|
||||||
"AppFlowy Cloud".to_string()
|
"AppFlowy Cloud".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn publish_view(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
payload: Vec<PublishViewPayload>,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let try_get_client = self.inner.try_get_client();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
let params = payload
|
||||||
|
.into_iter()
|
||||||
|
.map(|object| PublishCollabItem {
|
||||||
|
meta: PublishCollabMetadata {
|
||||||
|
view_id: Uuid::parse_str(object.meta.view_id.as_str()).unwrap_or(Uuid::nil()),
|
||||||
|
publish_name: object.meta.publish_name,
|
||||||
|
metadata: object.meta.metadata,
|
||||||
|
},
|
||||||
|
data: object.data,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
try_get_client?
|
||||||
|
.publish_collabs(&workspace_id, params)
|
||||||
|
.await
|
||||||
|
.map_err(FlowyError::from)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpublish_views(&self, workspace_id: &str, view_ids: Vec<String>) -> FutureResult<(), Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let try_get_client = self.inner.try_get_client();
|
||||||
|
let view_uuids = view_ids
|
||||||
|
.iter()
|
||||||
|
.map(|id| Uuid::parse_str(id).unwrap_or(Uuid::nil()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
try_get_client?
|
||||||
|
.unpublish_collabs(&workspace_id, &view_uuids)
|
||||||
|
.await
|
||||||
|
.map_err(FlowyError::from)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_info(&self, view_id: &str) -> FutureResult<PublishInfoResponse, Error> {
|
||||||
|
let try_get_client = self.inner.try_get_client();
|
||||||
|
let view_id = Uuid::parse_str(view_id)
|
||||||
|
.map_err(|_| FlowyError::new(ErrorCode::InvalidParams, "Invalid view id"));
|
||||||
|
|
||||||
|
FutureResult::new(async move {
|
||||||
|
let view_id = view_id?;
|
||||||
|
let info = try_get_client?
|
||||||
|
.get_published_collab_info(&view_id)
|
||||||
|
.await
|
||||||
|
.map_err(FlowyError::from)?;
|
||||||
|
Ok(PublishInfoResponse {
|
||||||
|
view_id: info.view_id.to_string(),
|
||||||
|
publish_name: info.publish_name,
|
||||||
|
namespace: info.namespace,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_publish_namespace(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
new_namespace: &str,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let namespace = new_namespace.to_string();
|
||||||
|
let try_get_client = self.inner.try_get_client();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
try_get_client?
|
||||||
|
.set_workspace_publish_namespace(&workspace_id, &namespace)
|
||||||
|
.await
|
||||||
|
.map_err(FlowyError::from)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_namespace(&self, workspace_id: &str) -> FutureResult<String, Error> {
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
let try_get_client = self.inner.try_get_client();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
let namespace = try_get_client?
|
||||||
|
.get_workspace_publish_namespace(&workspace_id)
|
||||||
|
.await
|
||||||
|
.map_err(FlowyError::from)?;
|
||||||
|
Ok(namespace)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ use flowy_folder_pub::cloud::{
|
|||||||
gen_workspace_id, FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot, Workspace,
|
gen_workspace_id, FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot, Workspace,
|
||||||
WorkspaceRecord,
|
WorkspaceRecord,
|
||||||
};
|
};
|
||||||
|
use flowy_folder_pub::entities::{PublishInfoResponse, PublishViewPayload};
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
|
|
||||||
use crate::local_server::LocalServerDB;
|
use crate::local_server::LocalServerDB;
|
||||||
@ -77,4 +78,48 @@ impl FolderCloudService for LocalServerFolderCloudServiceImpl {
|
|||||||
fn service_name(&self) -> String {
|
fn service_name(&self) -> String {
|
||||||
"Local".to_string()
|
"Local".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn publish_view(
|
||||||
|
&self,
|
||||||
|
_workspace_id: &str,
|
||||||
|
_payload: Vec<PublishViewPayload>,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
FutureResult::new(async { Err(anyhow!("Local server doesn't support publish view")) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpublish_views(
|
||||||
|
&self,
|
||||||
|
_workspace_id: &str,
|
||||||
|
_view_ids: Vec<String>,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
FutureResult::new(async { Err(anyhow!("Local server doesn't support unpublish views")) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_info(&self, _view_id: &str) -> FutureResult<PublishInfoResponse, Error> {
|
||||||
|
FutureResult::new(async move {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Local server doesn't support get publish info from remote"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_publish_namespace(
|
||||||
|
&self,
|
||||||
|
_workspace_id: &str,
|
||||||
|
_new_namespace: &str,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
FutureResult::new(async {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Local server doesn't support set publish namespace"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_namespace(&self, _workspace_id: &str) -> FutureResult<String, Error> {
|
||||||
|
FutureResult::new(async {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Local server doesn't support get publish namespace"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ use flowy_folder_pub::cloud::{
|
|||||||
gen_workspace_id, Folder, FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot,
|
gen_workspace_id, Folder, FolderCloudService, FolderCollabParams, FolderData, FolderSnapshot,
|
||||||
Workspace, WorkspaceRecord,
|
Workspace, WorkspaceRecord,
|
||||||
};
|
};
|
||||||
|
use flowy_folder_pub::entities::{PublishInfoResponse, PublishViewPayload};
|
||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
use lib_infra::util::timestamp;
|
use lib_infra::util::timestamp;
|
||||||
@ -174,6 +175,46 @@ where
|
|||||||
fn service_name(&self) -> String {
|
fn service_name(&self) -> String {
|
||||||
"Supabase".to_string()
|
"Supabase".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn publish_view(
|
||||||
|
&self,
|
||||||
|
_workspace_id: &str,
|
||||||
|
_payload: Vec<PublishViewPayload>,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
FutureResult::new(async { Err(anyhow!("supabase server doesn't support publish view")) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpublish_views(
|
||||||
|
&self,
|
||||||
|
_workspace_id: &str,
|
||||||
|
_view_ids: Vec<String>,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
FutureResult::new(async { Err(anyhow!("supabase server doesn't support unpublish views")) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_info(&self, _view_id: &str) -> FutureResult<PublishInfoResponse, Error> {
|
||||||
|
FutureResult::new(async { Err(anyhow!("supabase server doesn't support publish info")) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_publish_namespace(
|
||||||
|
&self,
|
||||||
|
_workspace_id: &str,
|
||||||
|
_new_namespace: &str,
|
||||||
|
) -> FutureResult<(), Error> {
|
||||||
|
FutureResult::new(async {
|
||||||
|
Err(anyhow!(
|
||||||
|
"supabase server doesn't support set publish namespace"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_publish_namespace(&self, _workspace_id: &str) -> FutureResult<String, Error> {
|
||||||
|
FutureResult::new(async {
|
||||||
|
Err(anyhow!(
|
||||||
|
"supabase server doesn't support get publish namespace"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_from_json_value(value: Value) -> Result<Workspace, Error> {
|
fn workspace_from_json_value(value: Value) -> Result<Workspace, Error> {
|
||||||
|
Loading…
Reference in New Issue
Block a user