diff --git a/libs/client-api/src/http_collab.rs b/libs/client-api/src/http_collab.rs index 76ed47b4..6d4c8ec7 100644 --- a/libs/client-api/src/http_collab.rs +++ b/libs/client-api/src/http_collab.rs @@ -2,7 +2,7 @@ use crate::http::log_request_id; use crate::{blocking_brotli_compress, brotli_compress, Client}; use app_error::AppError; use bytes::Bytes; -use client_api_entity::workspace_dto::{AFDatabase, ListDatabaseParam}; +use client_api_entity::workspace_dto::{AFDatabase, AFDatabaseRow, ListDatabaseParam}; use client_api_entity::{ BatchQueryCollabParams, BatchQueryCollabResult, CollabParams, CreateCollabParams, DeleteCollabParams, PublishCollabItem, QueryCollab, QueryCollabParams, UpdateCollabWebParams, @@ -172,6 +172,24 @@ impl Client { AppResponse::from_response(resp).await?.into_data() } + pub async fn list_database_row_ids( + &self, + workspace_id: &str, + database_id: &str, + ) -> Result, AppResponseError> { + let url = format!( + "{}/api/workspace/{}/database/{}/row", + self.base_url, workspace_id, database_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() + } + #[instrument(level = "debug", skip_all, err)] pub async fn post_realtime_msg( &self, diff --git a/libs/shared-entity/src/dto/workspace_dto.rs b/libs/shared-entity/src/dto/workspace_dto.rs index b07973b3..4382e91e 100644 --- a/libs/shared-entity/src/dto/workspace_dto.rs +++ b/libs/shared-entity/src/dto/workspace_dto.rs @@ -334,3 +334,8 @@ pub struct AFDatabaseMeta { pub name: String, pub icon: String, } + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct AFDatabaseRow { + pub id: String, +} diff --git a/src/api/workspace.rs b/src/api/workspace.rs index 1b0e68d2..222f11af 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -256,6 +256,10 @@ pub fn workspace_scope() -> Scope { .route(web::post().to(batch_get_collab_handler)), ) .service(web::resource("/{workspace_id}/database").route(web::get().to(list_database_handler))) + .service( + web::resource("/{workspace_id}/database/{database_id}/row") + .route(web::get().to(list_database_row_id_handler)), + ) } pub fn collab_scope() -> Scope { @@ -1874,6 +1878,25 @@ async fn list_database_handler( Ok(Json(AppResponse::Ok().with_data(dbs))) } +async fn list_database_row_id_handler( + user_uuid: UserUuid, + path_param: web::Path<(String, String)>, + state: Data, +) -> Result>>> { + let (workspace_id, db_id) = path_param.into_inner(); + let uid = state.user_cache.get_user_uid(&user_uuid).await?; + + state + .workspace_access_control + .enforce_action(&uid, &workspace_id, Action::Read) + .await?; + + let db_rows = + biz::collab::ops::list_database_row(&state.collab_access_control_storage, workspace_id, db_id) + .await?; + Ok(Json(AppResponse::Ok().with_data(db_rows))) +} + #[inline] async fn parser_realtime_msg( payload: Bytes, diff --git a/src/biz/collab/ops.rs b/src/biz/collab/ops.rs index adc929bd..d085ef6b 100644 --- a/src/biz/collab/ops.rs +++ b/src/biz/collab/ops.rs @@ -19,6 +19,7 @@ use database_entity::dto::QueryCollabResult; use database_entity::dto::{QueryCollab, QueryCollabParams}; use shared_entity::dto::workspace_dto::AFDatabase; use shared_entity::dto::workspace_dto::AFDatabaseField; +use shared_entity::dto::workspace_dto::AFDatabaseRow; use shared_entity::dto::workspace_dto::FavoriteFolderView; use shared_entity::dto::workspace_dto::RecentFolderView; use shared_entity::dto::workspace_dto::TrashFolderView; @@ -336,6 +337,24 @@ pub async fn get_latest_collab_encoded( .await } +pub async fn get_latest_collab( + storage: &CollabAccessControlStorage, + origin: GetCollabOrigin, + workspace_id: &str, + oid: &str, + collab_type: CollabType, +) -> Result { + let ec = get_latest_collab_encoded(storage, origin, workspace_id, oid, collab_type).await?; + let collab: Collab = Collab::new_with_source(CollabOrigin::Server, oid, ec.into(), vec![], false) + .map_err(|e| { + AppError::Internal(anyhow::anyhow!( + "Failed to create collab from encoded collab: {:?}", + e + )) + })?; + Ok(collab) +} + pub async fn get_published_view( collab_storage: &CollabAccessControlStorage, publish_namespace: String, @@ -368,7 +387,7 @@ pub async fn list_database( 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( + let mut ws_body_collab = get_latest_collab( collab_storage, GetCollabOrigin::Server, &workspace_uuid_str, @@ -376,23 +395,14 @@ pub async fn list_database( 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).map_err(|e| { + let ws_body = WorkspaceDatabaseBody::open(&mut ws_body_collab).map_err(|e| { AppError::Internal(anyhow::anyhow!( "Failed to open workspace database body: {:?}", e )) })?; - let db_metas = ws_body.get_all_meta(&collab.transact()); + let db_metas = ws_body.get_all_meta(&ws_body_collab.transact()); let query_collabs: Vec = db_metas .into_iter() .map(|meta| QueryCollab { @@ -404,7 +414,7 @@ pub async fn list_database( .batch_get_collab(&uid, query_collabs, true) .await; - let txn = collab.transact(); + let txn = ws_body_collab.transact(); let mut af_databases: Vec = Vec::with_capacity(results.len()); for (oid, result) in results { match result { @@ -463,3 +473,47 @@ pub async fn list_database( Ok(af_databases) } + +pub async fn list_database_row( + collab_storage: &CollabAccessControlStorage, + workspace_uuid_str: String, + database_uuid_str: String, +) -> Result, AppError> { + let db_collab = get_latest_collab( + collab_storage, + GetCollabOrigin::Server, + &workspace_uuid_str, + &database_uuid_str, + CollabType::Database, + ) + .await?; + let db_body = DatabaseBody::from_collab( + &db_collab, + Arc::new(NoPersistenceDatabaseCollabService), + None, + ) + .ok_or_else(|| { + AppError::Internal(anyhow::anyhow!( + "Failed to create database body from collab, db_collab_id: {}", + database_uuid_str, + )) + })?; + + // get any view_id + let txn = db_collab.transact(); + let iid = db_body.get_inline_view_id(&txn); + + let iview = db_body.views.get_view(&txn, &iid).ok_or_else(|| { + AppError::Internal(anyhow::anyhow!("Failed to get inline view, iid: {}", iid)) + })?; + + let db_rows = iview + .row_orders + .into_iter() + .map(|row_order| AFDatabaseRow { + id: row_order.id.to_string(), + }) + .collect(); + + Ok(db_rows) +} diff --git a/tests/workspace/workspace_crud.rs b/tests/workspace/workspace_crud.rs index 6e1835dd..7633311a 100644 --- a/tests/workspace/workspace_crud.rs +++ b/tests/workspace/workspace_crud.rs @@ -57,6 +57,14 @@ async fn workspace_list_database() { .await .unwrap(); assert_eq!(dbs.len(), 1); + { + let untitled_db = &dbs[0]; + let db_row_ids = c + .list_database_row_ids(&workspace_id, &untitled_db.id) + .await + .unwrap(); + assert_eq!(db_row_ids.len(), 5, "{:?}", db_row_ids); + } } { let dbs = c @@ -64,6 +72,14 @@ async fn workspace_list_database() { .await .unwrap(); assert_eq!(dbs.len(), 1); + { + let grid_db = &dbs[0]; + let db_row_ids = c + .list_database_row_ids(&workspace_id, &grid_db.id) + .await + .unwrap(); + assert_eq!(db_row_ids.len(), 5, "{:?}", db_row_ids); + } } }