feat: add database listings
This commit is contained in:
parent
b023e2f511
commit
6777ed8f5d
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::http::log_request_id;
|
use crate::http::log_request_id;
|
||||||
use crate::{blocking_brotli_compress, Client};
|
use crate::{blocking_brotli_compress, Client};
|
||||||
use app_error::AppError;
|
use app_error::AppError;
|
||||||
|
use client_api_entity::workspace_dto::AFDatabase;
|
||||||
use client_api_entity::{
|
use client_api_entity::{
|
||||||
BatchQueryCollabParams, BatchQueryCollabResult, CreateCollabParams, DeleteCollabParams,
|
BatchQueryCollabParams, BatchQueryCollabResult, CreateCollabParams, DeleteCollabParams,
|
||||||
QueryCollab,
|
QueryCollab,
|
||||||
|
|
@ -103,6 +104,7 @@ impl Client {
|
||||||
.await?
|
.await?
|
||||||
.into_data()
|
.into_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, err)]
|
#[instrument(level = "info", skip_all, err)]
|
||||||
pub async fn delete_collab(&self, params: DeleteCollabParams) -> Result<(), AppResponseError> {
|
pub async fn delete_collab(&self, params: DeleteCollabParams) -> Result<(), AppResponseError> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
|
|
@ -118,4 +120,21 @@ impl Client {
|
||||||
log_request_id(&resp);
|
log_request_id(&resp);
|
||||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all, err)]
|
||||||
|
pub async fn list_databases(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
) -> Result<Vec<AFDatabase>, AppResponseError> {
|
||||||
|
let url = format!("{}/api/workspace/{}/database", self.base_url, workspace_id);
|
||||||
|
let resp = self
|
||||||
|
.http_client_with_auth(Method::GET, &url)
|
||||||
|
.await?
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
log_request_id(&resp);
|
||||||
|
AppResponse::from_response(resp)
|
||||||
|
.await?
|
||||||
|
.into_data()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,3 +215,16 @@ pub struct PublishedView {
|
||||||
pub extra: Option<serde_json::Value>,
|
pub extra: Option<serde_json::Value>,
|
||||||
pub children: Vec<PublishedView>,
|
pub children: Vec<PublishedView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AFDatabase {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub fields: Vec<AFDatabaseField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct AFDatabaseField {
|
||||||
|
pub name: String,
|
||||||
|
pub field_type: String,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,7 @@ pub fn workspace_scope() -> Scope {
|
||||||
// Web browser can't carry payload when using GET method, so for browser compatibility, we use POST method
|
// Web browser can't carry payload when using GET method, so for browser compatibility, we use POST method
|
||||||
.route(web::post().to(batch_get_collab_handler)),
|
.route(web::post().to(batch_get_collab_handler)),
|
||||||
)
|
)
|
||||||
|
.service(web::resource("/{workspace_id}/database").route(web::get().to(list_database_handler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collab_scope() -> Scope {
|
pub fn collab_scope() -> Scope {
|
||||||
|
|
@ -1450,6 +1451,23 @@ async fn get_workspace_publish_outline_handler(
|
||||||
Ok(Json(AppResponse::Ok().with_data(published_view)))
|
Ok(Json(AppResponse::Ok().with_data(published_view)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn list_database_handler(
|
||||||
|
user_uuid: UserUuid,
|
||||||
|
workspace_id: web::Path<String>,
|
||||||
|
state: Data<AppState>,
|
||||||
|
) -> Result<Json<AppResponse<Vec<AFDatabase>>>> {
|
||||||
|
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||||
|
let workspace_id = workspace_id.into_inner();
|
||||||
|
let dbs = biz::collab::ops::list_database(
|
||||||
|
&state.pg_pool,
|
||||||
|
&state.collab_access_control_storage,
|
||||||
|
uid,
|
||||||
|
workspace_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(AppResponse::Ok().with_data(dbs)))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn parser_realtime_msg(
|
async fn parser_realtime_msg(
|
||||||
payload: Bytes,
|
payload: Bytes,
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,22 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use app_error::AppError;
|
use app_error::AppError;
|
||||||
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
|
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
|
||||||
|
use collab::preclude::Collab;
|
||||||
|
use collab_database::database::DatabaseBody;
|
||||||
|
use collab_database::entity::FieldType;
|
||||||
|
use collab_database::workspace_database::WorkspaceDatabaseBody;
|
||||||
use collab_entity::CollabType;
|
use collab_entity::CollabType;
|
||||||
use collab_entity::EncodedCollab;
|
use collab_entity::EncodedCollab;
|
||||||
use collab_folder::SectionItem;
|
use collab_folder::SectionItem;
|
||||||
use collab_folder::{CollabOrigin, Folder};
|
use collab_folder::{CollabOrigin, Folder};
|
||||||
|
use database::collab::select_workspace_database_oid;
|
||||||
use database::collab::{CollabStorage, GetCollabOrigin};
|
use database::collab::{CollabStorage, GetCollabOrigin};
|
||||||
use database::publish::select_published_view_ids_for_workspace;
|
use database::publish::select_published_view_ids_for_workspace;
|
||||||
use database::publish::select_workspace_id_for_publish_namespace;
|
use database::publish::select_workspace_id_for_publish_namespace;
|
||||||
|
use database_entity::dto::QueryCollabResult;
|
||||||
use database_entity::dto::{QueryCollab, QueryCollabParams};
|
use database_entity::dto::{QueryCollab, QueryCollabParams};
|
||||||
|
use shared_entity::dto::workspace_dto::AFDatabase;
|
||||||
|
use shared_entity::dto::workspace_dto::AFDatabaseField;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
|
|
@ -347,3 +355,90 @@ pub async fn get_published_view(
|
||||||
collab_folder_to_published_outline(&workspace_id.to_string(), &folder, &publish_view_ids)?;
|
collab_folder_to_published_outline(&workspace_id.to_string(), &folder, &publish_view_ids)?;
|
||||||
Ok(published_view)
|
Ok(published_view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list_database(
|
||||||
|
pg_pool: &PgPool,
|
||||||
|
collab_storage: &Arc<CollabAccessControlStorage>,
|
||||||
|
uid: i64,
|
||||||
|
workspace_uuid_str: String,
|
||||||
|
) -> Result<Vec<AFDatabase>, AppError> {
|
||||||
|
let workspace_uuid: Uuid = workspace_uuid_str.as_str().parse()?;
|
||||||
|
let ws_db_oid = select_workspace_database_oid(pg_pool, &workspace_uuid).await?;
|
||||||
|
|
||||||
|
let ec = get_latest_collab_encoded(
|
||||||
|
collab_storage.clone(),
|
||||||
|
GetCollabOrigin::Server,
|
||||||
|
&workspace_uuid_str,
|
||||||
|
&ws_db_oid,
|
||||||
|
CollabType::WorkspaceDatabase,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let mut collab: Collab =
|
||||||
|
Collab::new_with_source(CollabOrigin::Server, &ws_db_oid, ec.into(), vec![], false).map_err(
|
||||||
|
|e| {
|
||||||
|
AppError::Internal(anyhow::anyhow!(
|
||||||
|
"Failed to create collab from encoded collab: {:?}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let ws_body = WorkspaceDatabaseBody::open(&mut collab);
|
||||||
|
let db_metas = ws_body.get_all_database_meta(&collab.transact());
|
||||||
|
let query_collabs: Vec<QueryCollab> = db_metas
|
||||||
|
.into_iter()
|
||||||
|
.map(|meta| QueryCollab {
|
||||||
|
object_id: meta.database_id.clone(),
|
||||||
|
collab_type: CollabType::Database,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let results = collab_storage.batch_get_collab(&uid, query_collabs).await;
|
||||||
|
|
||||||
|
let txn = collab.transact();
|
||||||
|
let mut af_databases: Vec<AFDatabase> = Vec::with_capacity(results.len());
|
||||||
|
for (oid, result) in results {
|
||||||
|
match result {
|
||||||
|
QueryCollabResult::Success { encode_collab_v1 } => {
|
||||||
|
match EncodedCollab::decode_from_bytes(&encode_collab_v1) {
|
||||||
|
Ok(ec) => {
|
||||||
|
match Collab::new_with_source(CollabOrigin::Server, &oid, ec.into(), vec![], false) {
|
||||||
|
Ok(db_collab) => match DatabaseBody::from_collab(&db_collab) {
|
||||||
|
Some(db_body) => match db_body.metas.get_inline_view_id(&txn) {
|
||||||
|
Some(iid) => match db_body.views.get_view(&txn, &iid) {
|
||||||
|
Some(iview) => {
|
||||||
|
let name = iview.name;
|
||||||
|
|
||||||
|
let db_fields = db_body.fields.get_all_fields(&txn);
|
||||||
|
let mut af_fields: Vec<AFDatabaseField> = Vec::with_capacity(db_fields.len());
|
||||||
|
for db_field in db_fields {
|
||||||
|
af_fields.push(AFDatabaseField {
|
||||||
|
name: db_field.name,
|
||||||
|
field_type: format!("{:?}", FieldType::from(db_field.field_type)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
af_databases.push(AFDatabase {
|
||||||
|
id: db_body.get_database_id(&txn),
|
||||||
|
name,
|
||||||
|
fields: af_fields,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
None => tracing::warn!("Failed to get inline view: {}", iid),
|
||||||
|
},
|
||||||
|
None => tracing::error!("Failed to get inline view id for database: {}", oid),
|
||||||
|
},
|
||||||
|
None => tracing::error!("Failed to create db_body from db_collab, oid: {}", oid),
|
||||||
|
},
|
||||||
|
Err(err) => tracing::error!("Failed to create db_collab: {:?}", err),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => tracing::error!("Failed to decode collab: {:?}", err),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
QueryCollabResult::Failed { error } => {
|
||||||
|
tracing::warn!("Failed to get collab: {:?}", error)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(af_databases)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,42 @@
|
||||||
use client_api_test::generate_unique_registered_user_client;
|
use client_api_test::generate_unique_registered_user_client;
|
||||||
use collab_entity::CollabType;
|
use collab_entity::CollabType;
|
||||||
use database_entity::dto::QueryCollabParams;
|
use database_entity::dto::QueryCollabParams;
|
||||||
|
use shared_entity::dto::workspace_dto::AFDatabaseField;
|
||||||
use shared_entity::dto::workspace_dto::CreateWorkspaceParam;
|
use shared_entity::dto::workspace_dto::CreateWorkspaceParam;
|
||||||
use shared_entity::dto::workspace_dto::PatchWorkspaceParam;
|
use shared_entity::dto::workspace_dto::PatchWorkspaceParam;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn workspace_list_database() {
|
||||||
|
let (c, _user) = generate_unique_registered_user_client().await;
|
||||||
|
let workspace_id = c.get_workspaces().await.unwrap()[0].workspace_id;
|
||||||
|
let dbs = c.list_databases(&workspace_id.to_string()).await.unwrap();
|
||||||
|
assert_eq!(dbs.len(), 1);
|
||||||
|
|
||||||
|
let db = &dbs[0];
|
||||||
|
|
||||||
|
assert_eq!(db.name, "Untitled");
|
||||||
|
assert!(db.fields.contains(&AFDatabaseField {
|
||||||
|
name: "Last modified".to_string(),
|
||||||
|
field_type: "LastEditedTime".to_string(),
|
||||||
|
}));
|
||||||
|
assert!(db.fields.contains(&AFDatabaseField {
|
||||||
|
name: "Multiselect".to_string(),
|
||||||
|
field_type: "MultiSelect".to_string(),
|
||||||
|
}));
|
||||||
|
assert!(db.fields.contains(&AFDatabaseField {
|
||||||
|
name: "Tasks".to_string(),
|
||||||
|
field_type: "Checklist".to_string(),
|
||||||
|
}));
|
||||||
|
assert!(db.fields.contains(&AFDatabaseField {
|
||||||
|
name: "Status".to_string(),
|
||||||
|
field_type: "SingleSelect".to_string(),
|
||||||
|
}));
|
||||||
|
assert!(db.fields.contains(&AFDatabaseField {
|
||||||
|
name: "Description".to_string(),
|
||||||
|
field_type: "RichText".to_string(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn add_and_delete_workspace_for_user() {
|
async fn add_and_delete_workspace_for_user() {
|
||||||
let (c, _user) = generate_unique_registered_user_client().await;
|
let (c, _user) = generate_unique_registered_user_client().await;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue