Merge pull request #1020 from AppFlowy-IO/create-calendar-page
feat: add support for create database pages via API
This commit is contained in:
commit
3806a959b4
|
|
@ -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",
|
||||
|
|
|
|||
16
Cargo.toml
16
Cargo.toml
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue