2023-07-05 20:57:09 +08:00
|
|
|
use std::collections::HashMap;
|
2023-05-23 23:55:21 +08:00
|
|
|
|
2023-08-28 13:28:24 +08:00
|
|
|
use assert_json_diff::assert_json_eq;
|
|
|
|
use collab_database::rows::database_row_document_id_from_row_id;
|
2023-09-17 17:14:34 +08:00
|
|
|
use collab_define::CollabType;
|
2023-08-28 13:28:24 +08:00
|
|
|
use collab_document::blocks::DocumentData;
|
|
|
|
use collab_folder::core::FolderData;
|
2023-07-29 09:46:24 +08:00
|
|
|
use nanoid::nanoid;
|
2023-08-28 13:28:24 +08:00
|
|
|
use serde_json::json;
|
2023-07-29 09:46:24 +08:00
|
|
|
|
2023-08-28 13:28:24 +08:00
|
|
|
use flowy_core::DEFAULT_NAME;
|
2023-08-20 15:38:52 +08:00
|
|
|
use flowy_encrypt::decrypt_text;
|
2023-09-17 17:14:34 +08:00
|
|
|
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
|
2023-08-28 13:28:24 +08:00
|
|
|
use flowy_test::document::document_event::DocumentEventTest;
|
2023-07-05 20:57:09 +08:00
|
|
|
use flowy_test::event_builder::EventBuilder;
|
|
|
|
use flowy_test::FlowyCoreTest;
|
2023-10-02 17:22:22 +08:00
|
|
|
use flowy_user::entities::{AuthTypePB, OAuthPB, UpdateUserProfilePayloadPB, UserProfilePB};
|
2023-07-05 20:57:09 +08:00
|
|
|
use flowy_user::errors::ErrorCode;
|
2023-05-23 23:55:21 +08:00
|
|
|
use flowy_user::event_map::UserEvent::*;
|
2023-07-05 20:57:09 +08:00
|
|
|
|
|
|
|
use crate::util::*;
|
2023-05-23 23:55:21 +08:00
|
|
|
|
|
|
|
#[tokio::test]
|
2023-07-14 13:37:13 +08:00
|
|
|
async fn third_party_sign_up_test() {
|
2023-05-23 23:55:21 +08:00
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let test = FlowyCoreTest::new();
|
|
|
|
let mut map = HashMap::new();
|
2023-07-29 09:46:24 +08:00
|
|
|
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
|
|
|
|
map.insert(
|
|
|
|
USER_EMAIL.to_string(),
|
|
|
|
format!("{}@appflowy.io", nanoid!(6)),
|
|
|
|
);
|
2023-10-02 17:22:22 +08:00
|
|
|
let payload = OAuthPB {
|
2023-05-23 23:55:21 +08:00
|
|
|
map,
|
|
|
|
auth_type: AuthTypePB::Supabase,
|
|
|
|
};
|
|
|
|
|
|
|
|
let response = EventBuilder::new(test.clone())
|
2023-10-02 17:22:22 +08:00
|
|
|
.event(OAuth)
|
2023-05-23 23:55:21 +08:00
|
|
|
.payload(payload)
|
|
|
|
.async_send()
|
|
|
|
.await
|
|
|
|
.parse::<UserProfilePB>();
|
|
|
|
dbg!(&response);
|
|
|
|
}
|
|
|
|
}
|
2023-07-05 20:57:09 +08:00
|
|
|
|
2023-08-17 23:46:39 +08:00
|
|
|
#[tokio::test]
|
|
|
|
async fn third_party_sign_up_with_encrypt_test() {
|
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let test = FlowyCoreTest::new();
|
|
|
|
test.supabase_party_sign_up().await;
|
|
|
|
let user_profile = test.get_user_profile().await.unwrap();
|
|
|
|
assert!(user_profile.encryption_sign.is_empty());
|
|
|
|
|
|
|
|
let secret = test.enable_encryption().await;
|
|
|
|
let user_profile = test.get_user_profile().await.unwrap();
|
|
|
|
assert!(!user_profile.encryption_sign.is_empty());
|
|
|
|
|
2023-08-20 15:38:52 +08:00
|
|
|
let decryption_sign = decrypt_text(user_profile.encryption_sign, &secret).unwrap();
|
2023-08-17 23:46:39 +08:00
|
|
|
assert_eq!(decryption_sign, user_profile.id.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-29 09:46:24 +08:00
|
|
|
#[tokio::test]
|
|
|
|
async fn third_party_sign_up_with_duplicated_uuid() {
|
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let test = FlowyCoreTest::new();
|
|
|
|
let email = format!("{}@appflowy.io", nanoid!(6));
|
|
|
|
let mut map = HashMap::new();
|
|
|
|
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
|
|
|
|
map.insert(USER_EMAIL.to_string(), email.clone());
|
|
|
|
|
|
|
|
let response_1 = EventBuilder::new(test.clone())
|
2023-10-02 17:22:22 +08:00
|
|
|
.event(OAuth)
|
|
|
|
.payload(OAuthPB {
|
2023-07-29 09:46:24 +08:00
|
|
|
map: map.clone(),
|
|
|
|
auth_type: AuthTypePB::Supabase,
|
|
|
|
})
|
|
|
|
.async_send()
|
|
|
|
.await
|
|
|
|
.parse::<UserProfilePB>();
|
|
|
|
dbg!(&response_1);
|
|
|
|
|
|
|
|
let response_2 = EventBuilder::new(test.clone())
|
2023-10-02 17:22:22 +08:00
|
|
|
.event(OAuth)
|
|
|
|
.payload(OAuthPB {
|
2023-07-29 09:46:24 +08:00
|
|
|
map: map.clone(),
|
|
|
|
auth_type: AuthTypePB::Supabase,
|
|
|
|
})
|
|
|
|
.async_send()
|
|
|
|
.await
|
|
|
|
.parse::<UserProfilePB>();
|
|
|
|
assert_eq!(response_1, response_2);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn third_party_sign_up_with_duplicated_email() {
|
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let test = FlowyCoreTest::new();
|
|
|
|
let email = format!("{}@appflowy.io", nanoid!(6));
|
|
|
|
test
|
2023-10-02 17:22:22 +08:00
|
|
|
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
2023-07-29 09:46:24 +08:00
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let error = test
|
2023-10-02 17:22:22 +08:00
|
|
|
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
2023-07-29 09:46:24 +08:00
|
|
|
.await
|
|
|
|
.err()
|
|
|
|
.unwrap();
|
2023-08-22 00:19:15 +08:00
|
|
|
assert_eq!(error.code, ErrorCode::Conflict);
|
2023-07-29 09:46:24 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-07-14 13:37:13 +08:00
|
|
|
#[tokio::test]
|
|
|
|
async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
|
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let test = FlowyCoreTest::new_with_guest_user().await;
|
|
|
|
let old_views = test
|
|
|
|
.folder_manager
|
|
|
|
.get_current_workspace_views()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let old_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
|
|
|
|
|
|
|
let uuid = uuid::Uuid::new_v4().to_string();
|
2023-10-02 17:22:22 +08:00
|
|
|
test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
2023-07-14 13:37:13 +08:00
|
|
|
let new_views = test
|
|
|
|
.folder_manager
|
|
|
|
.get_current_workspace_views()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(old_views.len(), new_views.len());
|
|
|
|
assert_eq!(old_workspace.name, new_workspace.name);
|
|
|
|
assert_eq!(old_workspace.views.len(), new_workspace.views.len());
|
|
|
|
for (index, view) in old_views.iter().enumerate() {
|
|
|
|
assert_eq!(view.name, new_views[index].name);
|
|
|
|
assert_eq!(view.id, new_views[index].id);
|
|
|
|
assert_eq!(view.layout, new_views[index].layout);
|
|
|
|
assert_eq!(view.create_time, new_views[index].create_time);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
|
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let test = FlowyCoreTest::new_with_guest_user().await;
|
|
|
|
let uuid = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
2023-07-29 09:46:24 +08:00
|
|
|
let email = format!("{}@appflowy.io", nanoid!(6));
|
2023-07-14 13:37:13 +08:00
|
|
|
// The workspace of the guest will be migrated to the new user with given uuid
|
2023-07-29 09:46:24 +08:00
|
|
|
let _user_profile = test
|
2023-10-02 17:22:22 +08:00
|
|
|
.supabase_sign_up_with_uuid(&uuid, Some(email.clone()))
|
2023-07-29 09:46:24 +08:00
|
|
|
.await
|
|
|
|
.unwrap();
|
2023-07-14 13:37:13 +08:00
|
|
|
let old_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
|
|
|
let old_cloud_views = test
|
|
|
|
.folder_manager
|
|
|
|
.get_current_workspace_views()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(old_cloud_views.len(), 1);
|
|
|
|
assert_eq!(old_cloud_views.first().unwrap().child_views.len(), 1);
|
|
|
|
|
|
|
|
// sign out and then sign in as a guest
|
|
|
|
test.sign_out().await;
|
|
|
|
|
|
|
|
let _sign_up_context = test.sign_up_as_guest().await;
|
|
|
|
let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
|
|
|
test
|
|
|
|
.create_view(&new_workspace.id, "new workspace child view".to_string())
|
|
|
|
.await;
|
|
|
|
let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
|
|
|
assert_eq!(new_workspace.views.len(), 2);
|
|
|
|
|
|
|
|
// upload to cloud user with given uuid. This time the workspace of the guest will not be merged
|
|
|
|
// because the cloud user already has a workspace
|
2023-07-29 09:46:24 +08:00
|
|
|
test
|
2023-10-02 17:22:22 +08:00
|
|
|
.supabase_sign_up_with_uuid(&uuid, Some(email))
|
2023-07-29 09:46:24 +08:00
|
|
|
.await
|
|
|
|
.unwrap();
|
2023-07-14 13:37:13 +08:00
|
|
|
let new_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
|
|
|
let new_cloud_views = test
|
|
|
|
.folder_manager
|
|
|
|
.get_current_workspace_views()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(new_cloud_workspace, old_cloud_workspace);
|
|
|
|
assert_eq!(new_cloud_views, old_cloud_views);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-05 20:57:09 +08:00
|
|
|
#[tokio::test]
|
|
|
|
async fn check_not_exist_user_test() {
|
|
|
|
if let Some(test) = FlowySupabaseTest::new() {
|
|
|
|
let err = test
|
|
|
|
.check_user_with_uuid(&uuid::Uuid::new_v4().to_string())
|
|
|
|
.await
|
|
|
|
.unwrap_err();
|
2023-08-22 00:19:15 +08:00
|
|
|
assert_eq!(err.code, ErrorCode::RecordNotFound);
|
2023-07-05 20:57:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn get_user_profile_test() {
|
|
|
|
if let Some(test) = FlowySupabaseTest::new() {
|
|
|
|
let uuid = uuid::Uuid::new_v4().to_string();
|
2023-10-02 17:22:22 +08:00
|
|
|
test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
2023-07-05 20:57:09 +08:00
|
|
|
|
|
|
|
let result = test.get_user_profile().await;
|
|
|
|
assert!(result.is_ok());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn update_user_profile_test() {
|
|
|
|
if let Some(test) = FlowySupabaseTest::new() {
|
|
|
|
let uuid = uuid::Uuid::new_v4().to_string();
|
2023-10-02 17:22:22 +08:00
|
|
|
let profile = test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
2023-07-05 20:57:09 +08:00
|
|
|
test
|
|
|
|
.update_user_profile(UpdateUserProfilePayloadPB::new(profile.id).name("lucas"))
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let new_profile = test.get_user_profile().await.unwrap();
|
|
|
|
assert_eq!(new_profile.name, "lucas")
|
|
|
|
}
|
|
|
|
}
|
2023-07-29 09:46:24 +08:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn update_user_profile_with_existing_email_test() {
|
|
|
|
if let Some(test) = FlowySupabaseTest::new() {
|
|
|
|
let email = format!("{}@appflowy.io", nanoid!(6));
|
|
|
|
let _ = test
|
2023-10-02 17:22:22 +08:00
|
|
|
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
|
2023-07-29 09:46:24 +08:00
|
|
|
.await;
|
|
|
|
|
|
|
|
let profile = test
|
2023-10-02 17:22:22 +08:00
|
|
|
.supabase_sign_up_with_uuid(
|
2023-07-29 09:46:24 +08:00
|
|
|
&uuid::Uuid::new_v4().to_string(),
|
|
|
|
Some(format!("{}@appflowy.io", nanoid!(6))),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let error = test
|
|
|
|
.update_user_profile(
|
|
|
|
UpdateUserProfilePayloadPB::new(profile.id)
|
|
|
|
.name("lucas")
|
|
|
|
.email(&email),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2023-08-22 00:19:15 +08:00
|
|
|
assert_eq!(error.code, ErrorCode::Conflict);
|
2023-07-29 09:46:24 +08:00
|
|
|
}
|
|
|
|
}
|
2023-08-28 13:28:24 +08:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn migrate_anon_document_on_cloud_signup() {
|
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let test = FlowyCoreTest::new();
|
|
|
|
let user_profile = test.sign_up_as_guest().await.user_profile;
|
|
|
|
|
|
|
|
let view = test
|
|
|
|
.create_view(&user_profile.workspace_id, "My first view".to_string())
|
|
|
|
.await;
|
|
|
|
let document_event = DocumentEventTest::new_with_core(test.clone());
|
|
|
|
let block_id = document_event
|
|
|
|
.insert_index(&view.id, "hello world", 1, None)
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let _ = test.supabase_party_sign_up().await;
|
|
|
|
|
|
|
|
// After sign up, the documents should be migrated to the cloud
|
|
|
|
// So, we can get the document data from the cloud
|
|
|
|
let data: DocumentData = test
|
|
|
|
.document_manager
|
|
|
|
.get_cloud_service()
|
|
|
|
.get_document_data(&view.id)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
|
|
|
let block = data.blocks.get(&block_id).unwrap();
|
|
|
|
assert_json_eq!(
|
|
|
|
block.data,
|
|
|
|
json!({
|
|
|
|
"delta": [
|
|
|
|
{
|
|
|
|
"insert": "hello world"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn migrate_anon_data_on_cloud_signup() {
|
|
|
|
if get_supabase_config().is_some() {
|
|
|
|
let (cleaner, user_db_path) = unzip_history_user_db(
|
|
|
|
"./tests/user/supabase_test/history_user_db",
|
|
|
|
"workspace_sync",
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let test = FlowyCoreTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
|
|
|
|
let user_profile = test.supabase_party_sign_up().await;
|
|
|
|
|
|
|
|
// Get the folder data from remote
|
|
|
|
let folder_data: FolderData = test
|
|
|
|
.folder_manager
|
|
|
|
.get_cloud_service()
|
|
|
|
.get_folder_data(&user_profile.workspace_id)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let expected_folder_data = expected_workspace_sync_folder_data();
|
|
|
|
|
|
|
|
if folder_data.workspaces.len() != expected_folder_data.workspaces.len() {
|
|
|
|
dbg!(&folder_data.workspaces);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
folder_data.workspaces.len(),
|
|
|
|
expected_folder_data.workspaces.len()
|
|
|
|
);
|
|
|
|
assert_eq!(folder_data.views.len(), expected_folder_data.views.len());
|
|
|
|
|
|
|
|
// After migration, the ids of the folder_data should be different from the expected_folder_data
|
|
|
|
for i in 0..folder_data.views.len() {
|
|
|
|
let left_view = &folder_data.views[i];
|
|
|
|
let right_view = &expected_folder_data.views[i];
|
|
|
|
assert_ne!(left_view.id, right_view.id);
|
|
|
|
assert_ne!(left_view.parent_view_id, right_view.parent_view_id);
|
|
|
|
assert_eq!(left_view.name, right_view.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_ne!(
|
|
|
|
folder_data.current_workspace_id,
|
|
|
|
expected_folder_data.current_workspace_id
|
|
|
|
);
|
|
|
|
assert_ne!(folder_data.current_view, expected_folder_data.current_view);
|
|
|
|
|
|
|
|
let database_views = folder_data
|
|
|
|
.views
|
|
|
|
.iter()
|
|
|
|
.filter(|view| view.layout.is_database())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
// Try to load the database from the cloud.
|
|
|
|
for (i, database_view) in database_views.iter().enumerate() {
|
|
|
|
let cloud_service = test.database_manager.get_cloud_service();
|
|
|
|
let database_id = test
|
|
|
|
.database_manager
|
|
|
|
.get_database_id_with_view_id(&database_view.id)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let editor = test
|
|
|
|
.database_manager
|
|
|
|
.get_database(&database_id)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// The database view setting should be loaded by the view id
|
|
|
|
let _ = editor
|
|
|
|
.get_database_view_setting(&database_view.id)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let rows = editor.get_rows(&database_view.id).await.unwrap();
|
|
|
|
assert_eq!(rows.len(), 3);
|
|
|
|
|
|
|
|
if i == 0 {
|
|
|
|
let first_row = rows.first().unwrap().as_ref();
|
|
|
|
let icon_url = first_row.meta.icon_url.clone().unwrap();
|
|
|
|
assert_eq!(icon_url, "😄");
|
|
|
|
|
|
|
|
let document_id = database_row_document_id_from_row_id(&first_row.row.id);
|
|
|
|
let document_data: DocumentData = test
|
|
|
|
.document_manager
|
|
|
|
.get_cloud_service()
|
|
|
|
.get_document_data(&document_id)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let editor = test
|
|
|
|
.document_manager
|
|
|
|
.get_document(&document_id)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let expected_document_data = editor.lock().get_document_data().unwrap();
|
|
|
|
|
|
|
|
// let expected_document_data = test
|
|
|
|
// .document_manager
|
|
|
|
// .get_document_data(&document_id)
|
|
|
|
// .await
|
|
|
|
// .unwrap();
|
|
|
|
assert_eq!(document_data, expected_document_data);
|
|
|
|
let json = json!(document_data);
|
|
|
|
assert_eq!(
|
|
|
|
json["blocks"]["LPMpo0Qaab"]["data"]["delta"][0]["insert"],
|
|
|
|
json!("Row document")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert!(cloud_service
|
|
|
|
.get_collab_update(&database_id, CollabType::Database)
|
|
|
|
.await
|
|
|
|
.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
drop(cleaner);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn expected_workspace_sync_folder_data() -> FolderData {
|
|
|
|
serde_json::from_value::<FolderData>(json!({
|
|
|
|
"current_view": "e0811131-9928-4541-a174-20b7553d9e4c",
|
|
|
|
"current_workspace_id": "8df7f755-fa5d-480e-9f8e-48ea0fed12b3",
|
|
|
|
"views": [
|
|
|
|
{
|
|
|
|
"children": {
|
|
|
|
"items": [
|
|
|
|
{
|
|
|
|
"id": "e0811131-9928-4541-a174-20b7553d9e4c"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "53333949-c262-447b-8597-107589697059"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
"created_at": 1693147093,
|
|
|
|
"desc": "",
|
|
|
|
"icon": null,
|
|
|
|
"id": "e203afb3-de5d-458a-8380-33cd788a756e",
|
|
|
|
"is_favorite": false,
|
|
|
|
"layout": 0,
|
|
|
|
"name": "⭐️ Getting started",
|
|
|
|
"parent_view_id": "8df7f755-fa5d-480e-9f8e-48ea0fed12b3"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"children": {
|
|
|
|
"items": [
|
|
|
|
{
|
|
|
|
"id": "11c697ba-5ed1-41c0-adfc-576db28ad27b"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "4a5c25e2-a734-440c-973b-4c0e7ab0039c"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
"created_at": 1693147096,
|
|
|
|
"desc": "",
|
|
|
|
"icon": null,
|
|
|
|
"id": "e0811131-9928-4541-a174-20b7553d9e4c",
|
|
|
|
"is_favorite": false,
|
|
|
|
"layout": 1,
|
|
|
|
"name": "database",
|
|
|
|
"parent_view_id": "e203afb3-de5d-458a-8380-33cd788a756e"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"children": {
|
|
|
|
"items": []
|
|
|
|
},
|
|
|
|
"created_at": 1693147124,
|
|
|
|
"desc": "",
|
|
|
|
"icon": null,
|
|
|
|
"id": "11c697ba-5ed1-41c0-adfc-576db28ad27b",
|
|
|
|
"is_favorite": false,
|
|
|
|
"layout": 3,
|
|
|
|
"name": "calendar",
|
|
|
|
"parent_view_id": "e0811131-9928-4541-a174-20b7553d9e4c"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"children": {
|
|
|
|
"items": []
|
|
|
|
},
|
|
|
|
"created_at": 1693147125,
|
|
|
|
"desc": "",
|
|
|
|
"icon": null,
|
|
|
|
"id": "4a5c25e2-a734-440c-973b-4c0e7ab0039c",
|
|
|
|
"is_favorite": false,
|
|
|
|
"layout": 2,
|
|
|
|
"name": "board",
|
|
|
|
"parent_view_id": "e0811131-9928-4541-a174-20b7553d9e4c"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"children": {
|
|
|
|
"items": []
|
|
|
|
},
|
|
|
|
"created_at": 1693147133,
|
|
|
|
"desc": "",
|
|
|
|
"icon": null,
|
|
|
|
"id": "53333949-c262-447b-8597-107589697059",
|
|
|
|
"is_favorite": false,
|
|
|
|
"layout": 0,
|
|
|
|
"name": "document",
|
|
|
|
"parent_view_id": "e203afb3-de5d-458a-8380-33cd788a756e"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"workspaces": [
|
|
|
|
{
|
|
|
|
"child_views": {
|
|
|
|
"items": [
|
|
|
|
{
|
|
|
|
"id": "e203afb3-de5d-458a-8380-33cd788a756e"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
"created_at": 1693147093,
|
|
|
|
"id": "8df7f755-fa5d-480e-9f8e-48ea0fed12b3",
|
|
|
|
"name": "Workspace"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}))
|
|
|
|
.unwrap()
|
|
|
|
}
|