use std::sync::Arc; use app_error::AppError; use appflowy_collaborate::collab::storage::CollabAccessControlStorage; use collab_entity::CollabType; use collab_entity::EncodedCollab; use collab_folder::{CollabOrigin, Folder}; use database::collab::{CollabStorage, GetCollabOrigin}; use database::publish::select_published_view_ids_for_workspace; use database::publish::select_workspace_id_for_publish_namespace; use database_entity::dto::{QueryCollab, QueryCollabParams}; use sqlx::PgPool; use std::ops::DerefMut; use anyhow::Context; use shared_entity::dto::workspace_dto::{FolderView, PublishedView}; use sqlx::types::Uuid; use std::collections::HashSet; use tracing::{event, trace}; use validator::Validate; use access_control::collab::CollabAccessControl; use database_entity::dto::{ AFCollabMember, CollabMemberIdentify, InsertCollabMemberParams, QueryCollabMembers, UpdateCollabMemberParams, }; use super::folder_view::collab_folder_to_folder_view; use super::publish_outline::collab_folder_to_published_outline; /// Create a new collab member /// If the collab member already exists, return [AppError::RecordAlreadyExists] /// If the collab member does not exist, create a new one pub async fn create_collab_member( pg_pool: &PgPool, params: &InsertCollabMemberParams, collab_access_control: &impl CollabAccessControl, ) -> Result<(), AppError> { params.validate()?; let mut transaction = pg_pool .begin() .await .context("acquire transaction to insert collab member")?; if database::collab::is_collab_member_exists( params.uid, ¶ms.object_id, transaction.deref_mut(), ) .await? { return Err(AppError::RecordAlreadyExists(format!( "Collab member with uid {} and object_id {} already exists", params.uid, params.object_id ))); } trace!("Inserting collab member: {:?}", params); database::collab::insert_collab_member( params.uid, ¶ms.object_id, ¶ms.access_level, &mut transaction, ) .await?; collab_access_control .update_access_level_policy(¶ms.uid, ¶ms.object_id, params.access_level) .await?; transaction .commit() .await .context("fail to commit the transaction to insert collab member")?; Ok(()) } pub async fn upsert_collab_member( pg_pool: &PgPool, _user_uuid: &Uuid, params: &UpdateCollabMemberParams, collab_access_control: &impl CollabAccessControl, ) -> Result<(), AppError> { params.validate()?; let mut transaction = pg_pool .begin() .await .context("acquire transaction to upsert collab member")?; collab_access_control .update_access_level_policy(¶ms.uid, ¶ms.object_id, params.access_level) .await?; database::collab::insert_collab_member( params.uid, ¶ms.object_id, ¶ms.access_level, &mut transaction, ) .await?; transaction .commit() .await .context("fail to commit the transaction to upsert collab member")?; Ok(()) } pub async fn get_collab_member( pg_pool: &PgPool, params: &CollabMemberIdentify, ) -> Result { params.validate()?; let collab_member = database::collab::select_collab_member(¶ms.uid, ¶ms.object_id, pg_pool).await?; Ok(collab_member) } pub async fn delete_collab_member( pg_pool: &PgPool, params: &CollabMemberIdentify, collab_access_control: &impl CollabAccessControl, ) -> Result<(), AppError> { params.validate()?; let mut transaction = pg_pool .begin() .await .context("acquire transaction to remove collab member")?; event!( tracing::Level::DEBUG, "Deleting member:{} from {}", params.uid, params.object_id ); database::collab::delete_collab_member(params.uid, ¶ms.object_id, &mut transaction).await?; collab_access_control .remove_access_level(¶ms.uid, ¶ms.object_id) .await?; transaction .commit() .await .context("fail to commit the transaction to remove collab member")?; Ok(()) } pub async fn get_collab_member_list( pg_pool: &PgPool, params: &QueryCollabMembers, ) -> Result, AppError> { params.validate()?; let collab_member = database::collab::select_collab_members(¶ms.object_id, pg_pool).await?; Ok(collab_member) } pub async fn get_user_workspace_structure( collab_storage: Arc, pg_pool: &PgPool, uid: i64, workspace_id: Uuid, depth: u32, root_view_id: &str, ) -> Result { let depth_limit = 10; if depth > depth_limit { return Err(AppError::InvalidRequest(format!( "Depth {} is too large (limit: {})", depth, depth_limit ))); } 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 = publish_view_ids .into_iter() .map(|id| id.to_string()) .collect(); collab_folder_to_folder_view(root_view_id, &folder, depth, &publish_view_ids) } pub async fn get_latest_collab_folder( collab_storage: Arc, collab_origin: GetCollabOrigin, workspace_id: &str, ) -> Result { let folder_uid = if let GetCollabOrigin::User { uid } = collab_origin { uid } else { // Dummy uid to open the collab folder if the request does not originate from user 0 }; let encoded_collab = get_latest_collab_encoded( collab_storage, collab_origin, workspace_id, workspace_id, CollabType::Folder, ) .await?; let folder = Folder::from_collab_doc_state( folder_uid, CollabOrigin::Server, encoded_collab.into(), workspace_id, vec![], ) .map_err(|e| AppError::Unhandled(e.to_string()))?; Ok(folder) } pub async fn get_latest_collab_encoded( collab_storage: Arc, collab_origin: GetCollabOrigin, workspace_id: &str, oid: &str, collab_type: CollabType, ) -> Result { collab_storage .get_encode_collab( collab_origin, QueryCollabParams { workspace_id: workspace_id.to_string(), inner: QueryCollab { object_id: oid.to_string(), collab_type, }, }, true, ) .await } pub async fn get_published_view( collab_storage: Arc, publish_namespace: String, pg_pool: &PgPool, ) -> Result { let workspace_id = select_workspace_id_for_publish_namespace(pg_pool, &publish_namespace).await?; let folder = get_latest_collab_folder( collab_storage, GetCollabOrigin::Server, &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 = publish_view_ids .into_iter() .map(|id| id.to_string()) .collect(); let published_view: PublishedView = collab_folder_to_published_outline(&workspace_id.to_string(), &folder, &publish_view_ids)?; Ok(published_view) }