feat: add support for create database pages via API

This commit is contained in:
khorshuheng 2024-11-23 14:37:51 +08:00
parent 9e067e618b
commit 484cd595e0
10 changed files with 624 additions and 47 deletions

14
Cargo.lock generated
View File

@ -2171,7 +2171,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=be6bb90faac22ca8443a950ea3deafc7ec99b3a8#be6bb90faac22ca8443a950ea3deafc7ec99b3a8"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0efc824a6e1a56e4485646e6428c07fdccf6e918#0efc824a6e1a56e4485646e6428c07fdccf6e918"
dependencies = [
"anyhow",
"arc-swap",
@ -2196,7 +2196,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=be6bb90faac22ca8443a950ea3deafc7ec99b3a8#be6bb90faac22ca8443a950ea3deafc7ec99b3a8"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0efc824a6e1a56e4485646e6428c07fdccf6e918#0efc824a6e1a56e4485646e6428c07fdccf6e918"
dependencies = [
"anyhow",
"async-trait",
@ -2235,7 +2235,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=be6bb90faac22ca8443a950ea3deafc7ec99b3a8#be6bb90faac22ca8443a950ea3deafc7ec99b3a8"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0efc824a6e1a56e4485646e6428c07fdccf6e918#0efc824a6e1a56e4485646e6428c07fdccf6e918"
dependencies = [
"anyhow",
"arc-swap",
@ -2256,7 +2256,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=be6bb90faac22ca8443a950ea3deafc7ec99b3a8#be6bb90faac22ca8443a950ea3deafc7ec99b3a8"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0efc824a6e1a56e4485646e6428c07fdccf6e918#0efc824a6e1a56e4485646e6428c07fdccf6e918"
dependencies = [
"anyhow",
"bytes",
@ -2276,7 +2276,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=be6bb90faac22ca8443a950ea3deafc7ec99b3a8#be6bb90faac22ca8443a950ea3deafc7ec99b3a8"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0efc824a6e1a56e4485646e6428c07fdccf6e918#0efc824a6e1a56e4485646e6428c07fdccf6e918"
dependencies = [
"anyhow",
"arc-swap",
@ -2298,7 +2298,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=be6bb90faac22ca8443a950ea3deafc7ec99b3a8#be6bb90faac22ca8443a950ea3deafc7ec99b3a8"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0efc824a6e1a56e4485646e6428c07fdccf6e918#0efc824a6e1a56e4485646e6428c07fdccf6e918"
dependencies = [
"anyhow",
"async-recursion",
@ -2401,7 +2401,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=be6bb90faac22ca8443a950ea3deafc7ec99b3a8#be6bb90faac22ca8443a950ea3deafc7ec99b3a8"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0efc824a6e1a56e4485646e6428c07fdccf6e918#0efc824a6e1a56e4485646e6428c07fdccf6e918"
dependencies = [
"anyhow",
"collab",

View File

@ -304,18 +304,18 @@ debug = true
[profile.ci]
inherits = "release"
opt-level = 2
lto = false # Disable Link-Time Optimization
lto = false # Disable Link-Time Optimization
[patch.crates-io]
# It's diffcult to resovle different version with the same crate used in AppFlowy Frontend and the Client-API crate.
# So using patch to workaround this issue.
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "be6bb90faac22ca8443a950ea3deafc7ec99b3a8" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "be6bb90faac22ca8443a950ea3deafc7ec99b3a8" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "be6bb90faac22ca8443a950ea3deafc7ec99b3a8" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "be6bb90faac22ca8443a950ea3deafc7ec99b3a8" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "be6bb90faac22ca8443a950ea3deafc7ec99b3a8" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "be6bb90faac22ca8443a950ea3deafc7ec99b3a8" }
collab-importer = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "be6bb90faac22ca8443a950ea3deafc7ec99b3a8" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0efc824a6e1a56e4485646e6428c07fdccf6e918" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0efc824a6e1a56e4485646e6428c07fdccf6e918" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0efc824a6e1a56e4485646e6428c07fdccf6e918" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0efc824a6e1a56e4485646e6428c07fdccf6e918" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0efc824a6e1a56e4485646e6428c07fdccf6e918" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0efc824a6e1a56e4485646e6428c07fdccf6e918" }
collab-importer = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0efc824a6e1a56e4485646e6428c07fdccf6e918" }
[features]
history = []

View File

@ -49,6 +49,7 @@ pub trait CollabStorageAccessControl: Send + Sync + 'static {
async fn enforce_delete(&self, workspace_id: &str, uid: &i64, oid: &str) -> Result<(), AppError>;
}
#[derive(Clone)]
pub enum GetCollabOrigin {
User { uid: i64 },
Server,

View File

@ -274,6 +274,19 @@ pub enum ViewLayout {
Chat = 4,
}
impl std::fmt::Display for ViewLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ViewLayout::Document => "Document",
ViewLayout::Grid => "Grid",
ViewLayout::Board => "Board",
ViewLayout::Calendar => "Calendar",
ViewLayout::Chat => "Chat",
};
write!(f, "{}", s)
}
}
impl Default for ViewLayout {
fn default() -> Self {
Self::Document

View File

@ -5,6 +5,7 @@ use app_error::AppError;
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
use chrono::DateTime;
use chrono::Utc;
use collab::core::collab::DataSource;
use collab::preclude::Collab;
use collab_database::database::DatabaseBody;
use collab_database::entity::FieldType;
@ -12,6 +13,7 @@ use collab_database::fields::Field;
use collab_database::fields::TypeOptions;
use collab_database::rows::RowDetail;
use collab_database::workspace_database::NoPersistenceDatabaseCollabService;
use collab_database::workspace_database::WorkspaceDatabase;
use collab_database::workspace_database::WorkspaceDatabaseBody;
use collab_entity::CollabType;
use collab_entity::EncodedCollab;
@ -295,6 +297,30 @@ pub async fn get_user_workspace_structure(
collab_folder_to_folder_view(root_view_id, &folder, depth, &publish_view_ids)
}
pub async fn get_latest_workspace_database(
collab_storage: &CollabAccessControlStorage,
pg_pool: &PgPool,
collab_origin: GetCollabOrigin,
workspace_id: Uuid,
) -> Result<(String, WorkspaceDatabase), AppError> {
let workspace_database_oid = select_workspace_database_oid(pg_pool, &workspace_id).await?;
let workspace_database_collab = {
let encoded_collab = get_latest_collab_encoded(
collab_storage,
collab_origin,
&workspace_id.to_string(),
&workspace_database_oid,
CollabType::WorkspaceDatabase,
)
.await?;
collab_from_doc_state(encoded_collab.doc_state.to_vec(), &workspace_database_oid)?
};
let workspace_database = WorkspaceDatabase::open(workspace_database_collab)
.map_err(|err| AppError::Unhandled(format!("failed to open workspace database: {}", err)))?;
Ok((workspace_database_oid, workspace_database))
}
pub async fn get_latest_collab_folder(
collab_storage: &CollabAccessControlStorage,
collab_origin: GetCollabOrigin,
@ -736,3 +762,15 @@ fn add_to_selection_from_type_options(
}
};
}
pub fn collab_from_doc_state(doc_state: Vec<u8>, object_id: &str) -> Result<Collab, AppError> {
let collab = Collab::new_with_source(
CollabOrigin::Server,
object_id,
DataSource::DocStateV1(doc_state),
vec![],
false,
)
.map_err(|e| AppError::Unhandled(e.to_string()))?;
Ok(collab)
}

View File

@ -1,6 +1,4 @@
use authentication::jwt::OptionalUserUuid;
use collab::core::collab::DataSource;
use collab::preclude::Collab;
use collab_folder::CollabOrigin;
use collab_rt_entity::{ClientCollabMessage, UpdateSync};
use collab_rt_protocol::{Message, SyncMessage};
@ -750,15 +748,3 @@ pub async fn broadcast_update(
Ok(())
}
pub fn collab_from_doc_state(doc_state: Vec<u8>, object_id: &str) -> Result<Collab, AppError> {
let collab = Collab::new_with_source(
CollabOrigin::Server,
object_id,
DataSource::DocStateV1(doc_state),
vec![],
false,
)
.map_err(|e| AppError::Unhandled(e.to_string()))?;
Ok(collab)
}

View File

@ -3,6 +3,20 @@ use app_error::AppError;
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
use chrono::DateTime;
use collab::core::collab::Collab;
use collab_database::database::{
gen_database_group_id, gen_database_id, gen_field_id, gen_row_id, Database, DatabaseContext,
};
use collab_database::entity::{CreateDatabaseParams, CreateViewParams, EncodedDatabase, FieldType};
use collab_database::fields::select_type_option::{
SelectOption, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption,
};
use collab_database::fields::{default_field_settings_for_fields, Field};
use collab_database::rows::{new_cell_builder, CreateRowParams};
use collab_database::template::entity::CELL_DATA;
use collab_database::views::{
BoardLayoutSetting, CalendarLayoutSetting, DatabaseLayout, Group, GroupSetting, GroupSettingMap,
LayoutSetting, LayoutSettings,
};
use collab_database::workspace_database::{NoPersistenceDatabaseCollabService, WorkspaceDatabase};
use collab_database::{database::DatabaseBody, rows::RowId};
use collab_document::document::Document;
@ -32,12 +46,18 @@ use crate::biz::collab::folder_view::{
parse_extra_field_as_json, to_dto_view_icon, to_dto_view_layout, to_folder_view_icon,
to_space_permission,
};
use crate::biz::collab::ops::{collab_from_doc_state, get_latest_workspace_database};
use crate::biz::collab::{
folder_view::view_is_space,
ops::{get_latest_collab_encoded, get_latest_collab_folder},
};
use super::ops::{broadcast_update, collab_from_doc_state};
use super::ops::broadcast_update;
struct WorkspaceDatabaseUpdate {
pub updated_encoded_collab: Vec<u8>,
pub encoded_updates: Vec<u8>,
}
struct FolderUpdate {
pub updated_encoded_collab: Vec<u8>,
@ -140,20 +160,56 @@ pub async fn create_page(
view_layout: &ViewLayout,
name: Option<&str>,
) -> Result<Page, AppError> {
if *view_layout != ViewLayout::Document {
return Err(AppError::InvalidRequest(
"Only document layout is supported for page creation".to_string(),
));
match view_layout {
ViewLayout::Document => {
create_document_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
name,
)
.await
},
ViewLayout::Grid => {
create_grid_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
name,
)
.await
},
ViewLayout::Calendar => {
create_calendar_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
name,
)
.await
},
ViewLayout::Board => {
create_board_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
name,
)
.await
},
layout => Err(AppError::InvalidRequest(format!(
"The layout type {} is not supported for page creation",
layout
))),
}
create_document_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
name,
)
.await
}
fn prepare_default_document_collab_param() -> Result<CollabParams, AppError> {
@ -173,6 +229,175 @@ fn prepare_default_document_collab_param() -> Result<CollabParams, AppError> {
})
}
#[allow(clippy::too_many_arguments)]
async fn prepare_new_encoded_database(
view_id: &str,
database_id: &str,
name: &str,
fields: Vec<Field>,
rows: Vec<CreateRowParams>,
database_layout: DatabaseLayout,
layout_setting: Option<LayoutSetting>,
group_settings: Vec<GroupSettingMap>,
) -> Result<EncodedDatabase, AppError> {
let timestamp = collab_database::database::timestamp();
let context = DatabaseContext::new(Arc::new(NoPersistenceDatabaseCollabService));
let field_settings = default_field_settings_for_fields(&fields, database_layout);
let mut layout_settings = LayoutSettings::default();
if let Some(layout_setting) = layout_setting {
layout_settings.insert(database_layout, layout_setting);
}
let params = CreateDatabaseParams {
database_id: database_id.to_string(),
fields,
rows,
views: vec![CreateViewParams {
database_id: database_id.to_string(),
view_id: view_id.to_string(),
name: name.to_string(),
layout: database_layout,
layout_settings,
filters: vec![],
group_settings,
sorts: vec![],
field_settings,
created_at: timestamp,
modified_at: timestamp,
..Default::default()
}],
};
let database = Database::create_with_view(params, context)
.await
.map_err(|err| AppError::Internal(anyhow!("Failed to create database with view: {}", err)))?;
database
.encode_database_collabs()
.await
.map_err(|err| AppError::Internal(anyhow!("Failed to encode database: {}", err)))
}
async fn prepare_default_calendar_encoded_database(
view_id: &str,
database_id: &str,
name: &str,
) -> Result<EncodedDatabase, AppError> {
let text_field = Field::from_field_type("Title", FieldType::RichText, true);
let date_field = Field::from_field_type("Date", FieldType::DateTime, true);
let date_field_id = date_field.id.clone();
let multi_select_field = Field::from_field_type("Tags", FieldType::MultiSelect, true);
let fields = vec![text_field, date_field, multi_select_field];
let layout_setting = CalendarLayoutSetting::new(date_field_id);
prepare_new_encoded_database(
view_id,
database_id,
name,
fields,
vec![],
DatabaseLayout::Calendar,
Some(layout_setting.into()),
vec![],
)
.await
}
async fn prepare_default_grid_encoded_database(
view_id: &str,
database_id: &str,
name: &str,
) -> Result<EncodedDatabase, AppError> {
let text_field = Field::from_field_type("Name", FieldType::RichText, true);
let single_select_field = Field::from_field_type("Type", FieldType::SingleSelect, true);
let checkbox_field = Field::from_field_type("Done", FieldType::Checkbox, true);
let fields = vec![text_field, single_select_field, checkbox_field];
let rows = (0..3)
.map(|_| CreateRowParams::new(gen_row_id(), database_id.to_string()))
.collect();
prepare_new_encoded_database(
view_id,
database_id,
name,
fields,
rows,
DatabaseLayout::Grid,
None,
vec![],
)
.await
}
async fn prepare_default_board_encoded_database(
view_id: &str,
database_id: &str,
name: &str,
) -> Result<EncodedDatabase, AppError> {
let card_title_field = Field::from_field_type("Description", FieldType::RichText, true);
let text_field_id = card_title_field.id.clone();
let to_do_option = SelectOption::with_color("To Do", SelectOptionColor::Purple);
let doing_option = SelectOption::with_color("Doing", SelectOptionColor::Orange);
let done_option = SelectOption::with_color("Done", SelectOptionColor::Yellow);
let default_option_id = to_do_option.id.clone();
let options = vec![to_do_option, doing_option, done_option];
let card_status_option_ids: Vec<String> =
options.iter().map(|option| option.id.clone()).collect();
let mut card_status_options = SingleSelectTypeOption::default();
card_status_options.options.extend(options);
let mut card_status_field = Field::new(
gen_field_id(),
"Status".to_string(),
FieldType::SingleSelect.into(),
false,
);
card_status_field.type_options.insert(
FieldType::SingleSelect.to_string(),
card_status_options.into(),
);
let card_status_field_id = card_status_field.id.clone();
let card_status_field_type = card_status_field.field_type;
let mut group_ids = vec![card_status_field_id.clone()];
group_ids.extend(card_status_option_ids);
let groups = group_ids.iter().map(|id| Group::new(id.clone())).collect();
let group_settings: Vec<GroupSettingMap> = vec![GroupSetting {
id: gen_database_group_id(),
field_id: card_status_field_id.clone(),
field_type: card_status_field_type,
groups,
content: Default::default(),
}
.into()];
let mut rows = vec![];
let card_status_select_option_ids = SelectOptionIds::from(vec![default_option_id.clone()]);
for i in 0..3 {
let card_status_cell_data = card_status_select_option_ids.to_cell_data(FieldType::SingleSelect);
let mut description_cell = new_cell_builder(FieldType::RichText);
let description_text = format!("Card {}", i + 1);
description_cell.insert(CELL_DATA.into(), description_text.into());
let mut row = CreateRowParams::new(gen_row_id(), database_id.to_string());
row
.cells
.insert(card_status_field_id.clone(), card_status_cell_data);
row.cells.insert(text_field_id.clone(), description_cell);
rows.push(row);
}
let fields = vec![card_title_field, card_status_field];
let layout_setting = BoardLayoutSetting::new();
prepare_new_encoded_database(
view_id,
database_id,
name,
fields,
rows,
DatabaseLayout::Board,
Some(layout_setting.into()),
group_settings,
)
.await
}
#[allow(clippy::too_many_arguments)]
async fn add_new_space_to_folder(
uid: i64,
@ -248,17 +473,36 @@ async fn update_space_properties(
})
}
async fn add_new_database_to_workspace(
workspace_database: &mut WorkspaceDatabase,
database_id: &str,
view_id: &str,
) -> Result<WorkspaceDatabaseUpdate, AppError> {
let view_ids_by_database_id =
HashMap::from([(database_id.to_string(), vec![view_id.to_string()])]);
let encoded_updates = workspace_database
.batch_add_database(view_ids_by_database_id)
.encode_update_v1();
let updated_encoded_collab = workspace_database_to_encoded_collab(workspace_database)?;
Ok(WorkspaceDatabaseUpdate {
updated_encoded_collab,
encoded_updates,
})
}
async fn add_new_view_to_folder(
uid: i64,
parent_view_id: &str,
view_id: &str,
folder: &mut Folder,
name: Option<&str>,
layout: collab_folder::ViewLayout,
) -> Result<FolderUpdate, AppError> {
let encoded_update = {
let view = NestedChildViewBuilder::new(uid, parent_view_id.to_string())
.with_view_id(view_id)
.with_name(name.unwrap_or_default())
.with_layout(layout)
.build()
.view;
let mut txn = folder.collab.transact_mut();
@ -372,6 +616,55 @@ fn folder_to_encoded_collab(folder: &Folder) -> Result<Vec<u8>, AppError> {
})
}
fn workspace_database_to_encoded_collab(
workspace_db: &WorkspaceDatabase,
) -> Result<Vec<u8>, AppError> {
let encoded_workspace_db_collab = workspace_db
.encode_collab_v1()
.map_err(|err| AppError::Internal(anyhow!("Failed to encode workspace folder: {}", err)))?;
encoded_workspace_db_collab
.encode_to_bytes()
.map_err(|err| {
AppError::Internal(anyhow!(
"Failed to encode workspace folder to bytes: {}",
err
))
})
}
async fn insert_and_broadcast_workspace_database_update(
uid: i64,
workspace_id: Uuid,
workspace_database_id: &str,
workspace_database_update: WorkspaceDatabaseUpdate,
collab_storage: &CollabAccessControlStorage,
transaction: &mut Transaction<'_, sqlx::Postgres>,
) -> Result<(), AppError> {
let params = CollabParams {
object_id: workspace_database_id.to_string(),
encoded_collab_v1: workspace_database_update.updated_encoded_collab.into(),
collab_type: CollabType::WorkspaceDatabase,
embeddings: None,
};
let action_description = format!("Update workspace database: {}", workspace_id);
collab_storage
.insert_new_collab_with_transaction(
&workspace_id.to_string(),
&uid,
params,
transaction,
&action_description,
)
.await?;
broadcast_update(
collab_storage,
workspace_database_id,
workspace_database_update.encoded_updates.clone(),
)
.await?;
Ok(())
}
async fn insert_and_broadcast_workspace_folder_update(
uid: i64,
workspace_id: Uuid,
@ -417,8 +710,15 @@ async fn create_document_page(
let collab_origin = GetCollabOrigin::User { uid };
let mut folder =
get_latest_collab_folder(collab_storage, collab_origin, &workspace_id.to_string()).await?;
let folder_update =
add_new_view_to_folder(uid, parent_view_id, &view_id, &mut folder, name).await?;
let folder_update = add_new_view_to_folder(
uid,
parent_view_id,
&view_id,
&mut folder,
name,
collab_folder::ViewLayout::Document,
)
.await?;
let mut transaction = pg_pool.begin().await?;
let action = format!("Create new collab: {}", view_id);
collab_storage
@ -442,6 +742,170 @@ async fn create_document_page(
Ok(Page { view_id })
}
async fn create_grid_page(
pg_pool: &PgPool,
collab_storage: &CollabAccessControlStorage,
uid: i64,
workspace_id: Uuid,
parent_view_id: &str,
name: Option<&str>,
) -> Result<Page, AppError> {
let view_id = Uuid::new_v4().to_string();
let database_id = gen_database_id();
let default_grid_encoded_database =
prepare_default_grid_encoded_database(&view_id, &database_id, name.unwrap_or_default()).await?;
create_database_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
&view_id,
collab_folder::ViewLayout::Grid,
name,
&default_grid_encoded_database,
)
.await
}
async fn create_board_page(
pg_pool: &PgPool,
collab_storage: &CollabAccessControlStorage,
uid: i64,
workspace_id: Uuid,
parent_view_id: &str,
name: Option<&str>,
) -> Result<Page, AppError> {
let view_id = Uuid::new_v4().to_string();
let database_id = gen_database_id();
let default_board_encoded_database =
prepare_default_board_encoded_database(&view_id, &database_id, name.unwrap_or_default())
.await?;
create_database_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
&view_id,
collab_folder::ViewLayout::Board,
name,
&default_board_encoded_database,
)
.await
}
async fn create_calendar_page(
pg_pool: &PgPool,
collab_storage: &CollabAccessControlStorage,
uid: i64,
workspace_id: Uuid,
parent_view_id: &str,
name: Option<&str>,
) -> Result<Page, AppError> {
let view_id = Uuid::new_v4().to_string();
let database_id = gen_database_id();
let default_calendar_encoded_database =
prepare_default_calendar_encoded_database(&view_id, &database_id, name.unwrap_or_default())
.await?;
create_database_page(
pg_pool,
collab_storage,
uid,
workspace_id,
parent_view_id,
&view_id,
collab_folder::ViewLayout::Calendar,
name,
&default_calendar_encoded_database,
)
.await
}
#[allow(clippy::too_many_arguments)]
async fn create_database_page(
pg_pool: &PgPool,
collab_storage: &CollabAccessControlStorage,
uid: i64,
workspace_id: Uuid,
parent_view_id: &str,
view_id: &str,
view_layout: collab_folder::ViewLayout,
name: Option<&str>,
encoded_database: &EncodedDatabase,
) -> Result<Page, AppError> {
let collab_origin = GetCollabOrigin::User { uid };
let mut folder = get_latest_collab_folder(
collab_storage,
collab_origin.clone(),
&workspace_id.to_string(),
)
.await?;
let folder_update =
add_new_view_to_folder(uid, parent_view_id, view_id, &mut folder, name, view_layout).await?;
let (workspace_database_id, mut workspace_database) =
get_latest_workspace_database(collab_storage, pg_pool, collab_origin, workspace_id).await?;
let database_id = encoded_database.encoded_database_collab.object_id.clone();
let workspace_database_update =
add_new_database_to_workspace(&mut workspace_database, &database_id, view_id).await?;
let database_collab_params = CollabParams {
object_id: database_id.clone(),
encoded_collab_v1: encoded_database
.encoded_database_collab
.encoded_collab
.encode_to_bytes()?
.into(),
collab_type: CollabType::Database,
embeddings: None,
};
let row_collab_params_list = encoded_database
.encoded_row_collabs
.iter()
.map(|row_collab| CollabParams {
object_id: row_collab.object_id.clone(),
encoded_collab_v1: row_collab.encoded_collab.encode_to_bytes().unwrap().into(),
collab_type: CollabType::DatabaseRow,
embeddings: None,
})
.collect_vec();
let mut transaction = pg_pool.begin().await?;
let action = format!("Create new database collab: {}", database_id);
collab_storage
.insert_new_collab_with_transaction(
&workspace_id.to_string(),
&uid,
database_collab_params,
&mut transaction,
&action,
)
.await?;
collab_storage
.batch_insert_new_collab(&workspace_id.to_string(), &uid, row_collab_params_list)
.await?;
insert_and_broadcast_workspace_folder_update(
uid,
workspace_id,
folder_update,
collab_storage,
&mut transaction,
)
.await?;
insert_and_broadcast_workspace_database_update(
uid,
workspace_id,
&workspace_database_id,
workspace_database_update,
collab_storage,
&mut transaction,
)
.await?;
transaction.commit().await?;
Ok(Page {
view_id: view_id.to_string(),
})
}
pub async fn move_page_to_trash(
pg_pool: &PgPool,
collab_storage: &CollabAccessControlStorage,

View File

@ -43,10 +43,10 @@ use yrs::{Map, MapRef};
use crate::biz::collab::folder_view::to_folder_view_icon;
use crate::biz::collab::folder_view::to_folder_view_layout;
use crate::biz::collab::ops::collab_from_doc_state;
use crate::biz::collab::ops::get_latest_collab_encoded;
use super::ops::broadcast_update;
use super::ops::collab_from_doc_state;
#[allow(clippy::too_many_arguments)]
pub async fn duplicate_published_collab_to_workspace(

View File

@ -80,6 +80,81 @@ async fn get_page_view() {
assert_eq!(resp.data.row_data.len(), 0);
}
#[tokio::test]
async fn create_new_page_with_database() {
let (c, _user) = generate_unique_registered_user_client().await;
let workspaces = c.get_workspaces().await.unwrap();
assert_eq!(workspaces.len(), 1);
let workspace_id = workspaces[0].workspace_id;
let folder_view = c
.get_workspace_folder(&workspace_id.to_string(), Some(2), None)
.await
.unwrap();
let general_space = &folder_view
.children
.into_iter()
.find(|v| v.name == "General")
.unwrap();
let calendar_page = c
.create_workspace_page_view(
workspace_id,
&CreatePageParams {
parent_view_id: general_space.view_id.clone(),
layout: ViewLayout::Calendar,
name: Some("New calendar".to_string()),
},
)
.await
.unwrap();
let grid_page = c
.create_workspace_page_view(
workspace_id,
&CreatePageParams {
parent_view_id: general_space.view_id.clone(),
layout: ViewLayout::Grid,
name: Some("New grid".to_string()),
},
)
.await
.unwrap();
let board_page = c
.create_workspace_page_view(
workspace_id,
&CreatePageParams {
parent_view_id: general_space.view_id.clone(),
layout: ViewLayout::Grid,
name: Some("New board".to_string()),
},
)
.await
.unwrap();
sleep(Duration::from_secs(1)).await;
let folder_view = c
.get_workspace_folder(&workspace_id.to_string(), Some(2), None)
.await
.unwrap();
let general_space = &folder_view
.children
.into_iter()
.find(|v| v.name == "General")
.unwrap();
let views_under_general_space: HashSet<String> = general_space
.children
.iter()
.map(|v| v.view_id.clone())
.collect();
for view_id in &[
calendar_page.view_id.clone(),
grid_page.view_id.clone(),
board_page.view_id.clone(),
] {
assert!(views_under_general_space.contains(view_id));
c.get_workspace_page_view(workspace_id, view_id)
.await
.unwrap();
}
}
#[tokio::test]
async fn create_new_document_page() {
let (c, _user) = generate_unique_registered_user_client().await;

View File

@ -1,6 +1,6 @@
use app_error::ErrorCode;
use appflowy_cloud::biz::collab::folder_view::collab_folder_to_folder_view;
use appflowy_cloud::biz::workspace::ops::collab_from_doc_state;
use appflowy_cloud::biz::collab::ops::collab_from_doc_state;
use client_api::entity::{
AFRole, GlobalComment, PatchPublishedCollab, PublishCollabItem, PublishCollabMetadata,
PublishInfoMeta,