AppFlowy-Cloud/src/biz/collab/ops.rs

261 lines
7.0 KiB
Rust

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,
&params.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,
&params.object_id,
&params.access_level,
&mut transaction,
)
.await?;
collab_access_control
.update_access_level_policy(&params.uid, &params.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(&params.uid, &params.object_id, params.access_level)
.await?;
database::collab::insert_collab_member(
params.uid,
&params.object_id,
&params.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<AFCollabMember, AppError> {
params.validate()?;
let collab_member =
database::collab::select_collab_member(&params.uid, &params.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, &params.object_id, &mut transaction).await?;
collab_access_control
.remove_access_level(&params.uid, &params.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<Vec<AFCollabMember>, AppError> {
params.validate()?;
let collab_member = database::collab::select_collab_members(&params.object_id, pg_pool).await?;
Ok(collab_member)
}
pub async fn get_user_workspace_structure(
collab_storage: Arc<CollabAccessControlStorage>,
pg_pool: &PgPool,
uid: i64,
workspace_id: Uuid,
depth: u32,
root_view_id: &str,
) -> Result<FolderView, AppError> {
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<String> = 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<CollabAccessControlStorage>,
collab_origin: GetCollabOrigin,
workspace_id: &str,
) -> Result<Folder, AppError> {
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<CollabAccessControlStorage>,
collab_origin: GetCollabOrigin,
workspace_id: &str,
oid: &str,
collab_type: CollabType,
) -> Result<EncodedCollab, AppError> {
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<CollabAccessControlStorage>,
publish_namespace: String,
pg_pool: &PgPool,
) -> Result<PublishedView, AppError> {
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<String> = 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)
}