feat: add retrieve sections endpoints
This commit is contained in:
parent
96896101d4
commit
c686aa91a6
|
|
@ -4,6 +4,7 @@ use client_api_entity::auth_dto::DeleteUserQuery;
|
|||
use client_api_entity::workspace_dto::FolderView;
|
||||
use client_api_entity::workspace_dto::QueryWorkspaceFolder;
|
||||
use client_api_entity::workspace_dto::QueryWorkspaceParam;
|
||||
use client_api_entity::workspace_dto::SectionItems;
|
||||
use client_api_entity::AuthProvider;
|
||||
use client_api_entity::CollabType;
|
||||
use gotrue::grant::PasswordGrant;
|
||||
|
|
@ -719,6 +720,57 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
pub async fn get_workspace_favorite(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> Result<SectionItems, AppResponseError> {
|
||||
let url = format!("{}/api/workspace/{}/favorite", self.base_url, workspace_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<SectionItems>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
pub async fn get_workspace_recent(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> Result<SectionItems, AppResponseError> {
|
||||
let url = format!("{}/api/workspace/{}/recent", self.base_url, workspace_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<SectionItems>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
pub async fn get_workspace_trash(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> Result<SectionItems, AppResponseError> {
|
||||
let url = format!("{}/api/workspace/{}/trash", self.base_url, workspace_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<SectionItems>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(skip_all, err)]
|
||||
pub async fn sign_in_password(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -144,6 +144,11 @@ pub struct FolderView {
|
|||
pub children: Vec<FolderView>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SectionItems {
|
||||
pub views: Vec<FolderView>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
pub enum IconType {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ use crate::api::util::PayloadReader;
|
|||
use crate::api::util::{compress_type_from_header_value, device_id_from_headers, CollabValidator};
|
||||
use crate::api::ws::RealtimeServerAddr;
|
||||
use crate::biz;
|
||||
use crate::biz::collab::ops::{
|
||||
get_user_favorite_folder_views, get_user_recent_folder_views, get_user_trash_folder_views,
|
||||
};
|
||||
use crate::biz::workspace;
|
||||
use crate::biz::workspace::ops::{
|
||||
create_comment_on_published_view, create_reaction_on_comment, get_comments_on_published_view,
|
||||
|
|
@ -174,6 +177,11 @@ pub fn workspace_scope() -> Scope {
|
|||
.service(
|
||||
web::resource("/{workspace_id}/folder").route(web::get().to(get_workspace_folder_handler)),
|
||||
)
|
||||
.service(web::resource("/{workspace_id}/recent").route(web::get().to(get_recent_views_handler)))
|
||||
.service(
|
||||
web::resource("/{workspace_id}/favorite").route(web::get().to(get_favorite_views_handler)),
|
||||
)
|
||||
.service(web::resource("/{workspace_id}/trash").route(web::get().to(get_trash_views_handler)))
|
||||
.service(
|
||||
web::resource("/published-outline/{publish_namespace}")
|
||||
.route(web::get().to(get_workspace_publish_outline_handler)),
|
||||
|
|
@ -1322,6 +1330,65 @@ async fn get_workspace_folder_handler(
|
|||
Ok(Json(AppResponse::Ok().with_data(folder_view)))
|
||||
}
|
||||
|
||||
async fn get_recent_views_handler(
|
||||
user_uuid: UserUuid,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<SectionItems>>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let folder_views = get_user_recent_folder_views(
|
||||
state.collab_access_control_storage.clone(),
|
||||
&state.pg_pool,
|
||||
uid,
|
||||
workspace_id,
|
||||
)
|
||||
.await?;
|
||||
let section_items = SectionItems {
|
||||
views: folder_views,
|
||||
};
|
||||
Ok(Json(AppResponse::Ok().with_data(section_items)))
|
||||
}
|
||||
|
||||
async fn get_favorite_views_handler(
|
||||
user_uuid: UserUuid,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<SectionItems>>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let folder_views = get_user_favorite_folder_views(
|
||||
state.collab_access_control_storage.clone(),
|
||||
&state.pg_pool,
|
||||
uid,
|
||||
workspace_id,
|
||||
)
|
||||
.await?;
|
||||
let section_items = SectionItems {
|
||||
views: folder_views,
|
||||
};
|
||||
Ok(Json(AppResponse::Ok().with_data(section_items)))
|
||||
}
|
||||
|
||||
async fn get_trash_views_handler(
|
||||
user_uuid: UserUuid,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<SectionItems>>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let folder_views = get_user_trash_folder_views(
|
||||
state.collab_access_control_storage.clone(),
|
||||
uid,
|
||||
workspace_id,
|
||||
)
|
||||
.await?;
|
||||
let section_items = SectionItems {
|
||||
views: folder_views,
|
||||
};
|
||||
Ok(Json(AppResponse::Ok().with_data(section_items)))
|
||||
}
|
||||
|
||||
async fn get_workspace_publish_outline_handler(
|
||||
publish_namespace: web::Path<String>,
|
||||
state: Data<AppState>,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||
|
||||
use app_error::AppError;
|
||||
use chrono::DateTime;
|
||||
use collab_folder::{Folder, ViewLayout as CollabFolderViewLayout};
|
||||
use collab_folder::{Folder, SectionItem, ViewLayout as CollabFolderViewLayout};
|
||||
use shared_entity::dto::workspace_dto::{FolderView, ViewLayout};
|
||||
|
||||
/// Return all folders belonging to a workspace, excluding private sections which the user does not have access to.
|
||||
|
|
@ -107,14 +107,39 @@ fn to_folder_view(
|
|||
is_private,
|
||||
is_published: published_view_ids.contains(view_id),
|
||||
layout: to_view_layout(&view.layout),
|
||||
created_at: DateTime::from_timestamp(view.created_at, 0).unwrap_or(DateTime::default()),
|
||||
last_edited_time: DateTime::from_timestamp(view.last_edited_time, 0)
|
||||
.unwrap_or(DateTime::default()),
|
||||
created_at: DateTime::from_timestamp(view.created_at, 0).unwrap_or_default(),
|
||||
last_edited_time: DateTime::from_timestamp(view.last_edited_time, 0).unwrap_or_default(),
|
||||
extra,
|
||||
children,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn section_items_to_folder_view(
|
||||
section_items: &[SectionItem],
|
||||
folder: &Folder,
|
||||
published_view_ids: &HashSet<String>,
|
||||
) -> Vec<FolderView> {
|
||||
section_items
|
||||
.iter()
|
||||
.filter_map(|section_item| {
|
||||
let view = folder.get_view(§ion_item.id);
|
||||
view.map(|v| FolderView {
|
||||
view_id: v.id.clone(),
|
||||
name: v.name.clone(),
|
||||
icon: v.icon.as_ref().map(|icon| to_dto_view_icon(icon.clone())),
|
||||
is_space: false,
|
||||
is_private: false,
|
||||
is_published: published_view_ids.contains(&v.id),
|
||||
created_at: DateTime::from_timestamp(v.created_at, 0).unwrap_or_default(),
|
||||
last_edited_time: DateTime::from_timestamp(v.last_edited_time, 0).unwrap_or_default(),
|
||||
layout: to_view_layout(&v.layout),
|
||||
extra: v.extra.as_ref().map(|e| parse_extra_field_as_json(e)),
|
||||
children: vec![],
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn view_is_space(view: &collab_folder::View) -> bool {
|
||||
let extra = match view.extra.as_ref() {
|
||||
Some(extra) => extra,
|
||||
|
|
@ -133,6 +158,13 @@ pub fn view_is_space(view: &collab_folder::View) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_extra_field_as_json(extra: &str) -> serde_json::Value {
|
||||
serde_json::from_str::<serde_json::Value>(extra).unwrap_or_else(|e| {
|
||||
tracing::warn!("failed to parse extra field({}): {}", extra, e);
|
||||
serde_json::Value::Null
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_dto_view_icon(
|
||||
icon: collab_folder::ViewIcon,
|
||||
) -> shared_entity::dto::workspace_dto::ViewIcon {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use app_error::AppError;
|
|||
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
|
||||
use collab_entity::CollabType;
|
||||
use collab_entity::EncodedCollab;
|
||||
use collab_folder::SectionItem;
|
||||
use collab_folder::{CollabOrigin, Folder};
|
||||
use database::collab::{CollabStorage, GetCollabOrigin};
|
||||
use database::publish::select_published_view_ids_for_workspace;
|
||||
|
|
@ -27,6 +28,7 @@ use database_entity::dto::{
|
|||
};
|
||||
|
||||
use super::folder_view::collab_folder_to_folder_view;
|
||||
use super::folder_view::section_items_to_folder_view;
|
||||
use super::publish_outline::collab_folder_to_published_outline;
|
||||
|
||||
/// Create a new collab member
|
||||
|
|
@ -156,6 +158,93 @@ pub async fn get_collab_member_list(
|
|||
Ok(collab_member)
|
||||
}
|
||||
|
||||
pub async fn get_user_favorite_folder_views(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
pg_pool: &PgPool,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
) -> Result<Vec<FolderView>, AppError> {
|
||||
let folder = get_latest_collab_folder(
|
||||
collab_storage,
|
||||
GetCollabOrigin::User { uid },
|
||||
&workspace_id.to_string(),
|
||||
)
|
||||
.await?;
|
||||
let publish_view_ids = select_published_view_ids_for_workspace(pg_pool, workspace_id).await?;
|
||||
let publish_view_ids: HashSet<String> = publish_view_ids
|
||||
.into_iter()
|
||||
.map(|id| id.to_string())
|
||||
.collect();
|
||||
let deleted_section_item_ids: Vec<String> = folder
|
||||
.get_my_trash_sections()
|
||||
.iter()
|
||||
.map(|s| s.id.clone())
|
||||
.collect();
|
||||
let favorite_section_items: Vec<SectionItem> = folder
|
||||
.get_my_favorite_sections()
|
||||
.into_iter()
|
||||
.filter(|s| !deleted_section_item_ids.contains(&s.id))
|
||||
.collect();
|
||||
Ok(section_items_to_folder_view(
|
||||
&favorite_section_items,
|
||||
&folder,
|
||||
&publish_view_ids,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_user_recent_folder_views(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
pg_pool: &PgPool,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
) -> Result<Vec<FolderView>, AppError> {
|
||||
let folder = get_latest_collab_folder(
|
||||
collab_storage,
|
||||
GetCollabOrigin::User { uid },
|
||||
&workspace_id.to_string(),
|
||||
)
|
||||
.await?;
|
||||
let deleted_section_item_ids: Vec<String> = folder
|
||||
.get_my_trash_sections()
|
||||
.iter()
|
||||
.map(|s| s.id.clone())
|
||||
.collect();
|
||||
let recent_section_items: Vec<SectionItem> = folder
|
||||
.get_my_recent_sections()
|
||||
.into_iter()
|
||||
.filter(|s| !deleted_section_item_ids.contains(&s.id))
|
||||
.collect();
|
||||
let publish_view_ids = select_published_view_ids_for_workspace(pg_pool, workspace_id).await?;
|
||||
let publish_view_ids: HashSet<String> = publish_view_ids
|
||||
.into_iter()
|
||||
.map(|id| id.to_string())
|
||||
.collect();
|
||||
Ok(section_items_to_folder_view(
|
||||
&recent_section_items,
|
||||
&folder,
|
||||
&publish_view_ids,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_user_trash_folder_views(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
) -> Result<Vec<FolderView>, AppError> {
|
||||
let folder = get_latest_collab_folder(
|
||||
collab_storage,
|
||||
GetCollabOrigin::User { uid },
|
||||
&workspace_id.to_string(),
|
||||
)
|
||||
.await?;
|
||||
let section_items = folder.get_my_trash_sections();
|
||||
Ok(section_items_to_folder_view(
|
||||
§ion_items,
|
||||
&folder,
|
||||
&HashSet::default(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_user_workspace_structure(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
pg_pool: &PgPool,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use client_api::entity::{CreateCollabParams, QueryCollabParams};
|
||||
use client_api_test::generate_unique_registered_user_client;
|
||||
use collab::core::origin::CollabClient;
|
||||
use collab_folder::{CollabOrigin, Folder};
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_workpace_folder() {
|
||||
|
|
@ -31,3 +34,65 @@ async fn get_workpace_folder() {
|
|||
.unwrap();
|
||||
assert_eq!(folder_view.children.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_section_items() {
|
||||
let (c, _user) = generate_unique_registered_user_client().await;
|
||||
let user_workspace_info = c.get_user_workspace_info().await.unwrap();
|
||||
let workspaces = c.get_workspaces().await.unwrap();
|
||||
assert_eq!(workspaces.len(), 1);
|
||||
let workspace_id = workspaces[0].workspace_id.to_string();
|
||||
let folder_collab = c
|
||||
.get_collab(QueryCollabParams::new(
|
||||
workspace_id.clone(),
|
||||
collab_entity::CollabType::Folder,
|
||||
workspace_id.clone(),
|
||||
))
|
||||
.await
|
||||
.unwrap()
|
||||
.encode_collab;
|
||||
let uid = user_workspace_info.user_profile.uid;
|
||||
let mut folder = Folder::from_collab_doc_state(
|
||||
uid,
|
||||
CollabOrigin::Client(CollabClient::new(uid, c.device_id.clone())),
|
||||
folder_collab.into(),
|
||||
&workspace_id,
|
||||
vec![],
|
||||
)
|
||||
.unwrap();
|
||||
let views = folder.get_views_belong_to(&workspace_id);
|
||||
let new_favorite_id = views[0].children[0].id.clone();
|
||||
let to_be_deleted_favorite_id = views[0].children[1].id.clone();
|
||||
folder.add_favorite_view_ids(vec![
|
||||
new_favorite_id.clone(),
|
||||
to_be_deleted_favorite_id.clone(),
|
||||
]);
|
||||
folder.add_trash_view_ids(vec![to_be_deleted_favorite_id.clone()]);
|
||||
let recent_id = folder.get_views_belong_to(&new_favorite_id)[0].id.clone();
|
||||
folder.add_recent_view_ids(vec![recent_id.clone()]);
|
||||
let collab_type = collab_entity::CollabType::Folder;
|
||||
c.update_collab(CreateCollabParams {
|
||||
workspace_id: workspace_id.clone(),
|
||||
collab_type: collab_type.clone(),
|
||||
object_id: workspace_id.clone(),
|
||||
encoded_collab_v1: folder
|
||||
.encode_collab_v1(|collab| collab_type.validate_require_data(collab))
|
||||
.unwrap()
|
||||
.encode_to_bytes()
|
||||
.unwrap(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let favorite_section_items = c.get_workspace_favorite(&workspace_id).await.unwrap();
|
||||
assert_eq!(favorite_section_items.views.len(), 1);
|
||||
assert_eq!(favorite_section_items.views[0].view_id, new_favorite_id);
|
||||
let trash_section_items = c.get_workspace_trash(&workspace_id).await.unwrap();
|
||||
assert_eq!(trash_section_items.views.len(), 1);
|
||||
assert_eq!(
|
||||
trash_section_items.views[0].view_id,
|
||||
to_be_deleted_favorite_id
|
||||
);
|
||||
let recent_section_items = c.get_workspace_recent(&workspace_id).await.unwrap();
|
||||
assert_eq!(recent_section_items.views.len(), 1);
|
||||
assert_eq!(recent_section_items.views[0].view_id, recent_id);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue