Skip to content

Commit

Permalink
Merge pull request #1259 from AppFlowy-IO/duplicate-api
Browse files Browse the repository at this point in the history
feat: api for duplicate view
  • Loading branch information
khorshuheng authored Mar 5, 2025
2 parents 58c6e60 + aec0deb commit 8daa5b3
Show file tree
Hide file tree
Showing 10 changed files with 691 additions and 18 deletions.
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,13 @@ lto = false
[patch.crates-io]
# It's diffcult to resovle different version with the same crate used in AppFlowy Frontend and the Client-API crate.
# So using patch to workaround this issue.
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c2442a93704e14508ccee325da8d56ef0b34c7ce" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c2442a93704e14508ccee325da8d56ef0b34c7ce" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c2442a93704e14508ccee325da8d56ef0b34c7ce" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c2442a93704e14508ccee325da8d56ef0b34c7ce" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c2442a93704e14508ccee325da8d56ef0b34c7ce" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c2442a93704e14508ccee325da8d56ef0b34c7ce" }
collab-importer = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c2442a93704e14508ccee325da8d56ef0b34c7ce" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "45239d2ae871cc355ea2cc1d5d578e21c8263242" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "45239d2ae871cc355ea2cc1d5d578e21c8263242" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "45239d2ae871cc355ea2cc1d5d578e21c8263242" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "45239d2ae871cc355ea2cc1d5d578e21c8263242" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "45239d2ae871cc355ea2cc1d5d578e21c8263242" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "45239d2ae871cc355ea2cc1d5d578e21c8263242" }
collab-importer = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "45239d2ae871cc355ea2cc1d5d578e21c8263242" }

[features]
history = []
Expand Down
22 changes: 21 additions & 1 deletion libs/client-api/src/http_view.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use client_api_entity::workspace_dto::{
AppendBlockToPageParams, CreatePageDatabaseViewParams, CreatePageParams, CreateSpaceParams,
MovePageParams, Page, PageCollab, PublishPageParams, Space, UpdatePageParams, UpdateSpaceParams,
DuplicatePageParams, MovePageParams, Page, PageCollab, PublishPageParams, Space,
UpdatePageParams, UpdateSpaceParams,
};
use reqwest::Method;
use serde_json::json;
Expand Down Expand Up @@ -277,4 +278,23 @@ impl Client {
.await?;
AppResponse::<()>::from_response(resp).await?.into_error()
}

pub async fn duplicate_view_and_children(
&self,
workspace_id: Uuid,
view_id: &str,
params: &DuplicatePageParams,
) -> Result<(), AppResponseError> {
let url = format!(
"{}/api/workspace/{}/page-view/{}/duplicate",
self.base_url, workspace_id, view_id
);
let resp = self
.http_client_with_auth(Method::POST, &url)
.await?
.json(params)
.send()
.await?;
AppResponse::<()>::from_response(resp).await?.into_error()
}
}
5 changes: 5 additions & 0 deletions libs/shared-entity/src/dto/workspace_dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ pub struct MovePageParams {
pub prev_view_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DuplicatePageParams {
pub suffix: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreatePageDatabaseViewParams {
pub layout: ViewLayout,
Expand Down
32 changes: 32 additions & 0 deletions src/api/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::biz::collab::ops::{
use crate::biz::collab::utils::collab_from_doc_state;
use crate::biz::user::user_verify::verify_token;
use crate::biz::workspace;
use crate::biz::workspace::duplicate::duplicate_view_tree_and_collab;
use crate::biz::workspace::ops::{
create_comment_on_published_view, create_reaction_on_comment, get_comments_on_published_view,
get_reactions_on_published_view, remove_comment_on_published_view, remove_reaction_on_comment,
Expand Down Expand Up @@ -184,6 +185,10 @@ pub fn workspace_scope() -> Scope {
web::resource("/{workspace_id}/page-view/{view_id}/move")
.route(web::post().to(move_page_handler)),
)
.service(
web::resource("/{workspace_id}/page-view/{view_id}/duplicate")
.route(web::post().to(duplicate_page_handler)),
)
.service(
web::resource("/{workspace_id}/page-view/{view_id}/database-view")
.route(web::post().to(post_page_database_view_handler)),
Expand Down Expand Up @@ -1284,6 +1289,33 @@ async fn move_page_handler(
Ok(Json(AppResponse::Ok()))
}

async fn duplicate_page_handler(
user_uuid: UserUuid,
path: web::Path<(Uuid, Uuid)>,
payload: Json<DuplicatePageParams>,
state: Data<AppState>,
server: Data<RealtimeServerAddr>,
req: HttpRequest,
) -> Result<Json<AppResponse<()>>> {
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
let (workspace_uuid, view_id) = path.into_inner();
let user = realtime_user_for_web_request(req.headers(), uid)?;
let suffix = payload.suffix.as_deref().unwrap_or(" (Copy)").to_string();
duplicate_view_tree_and_collab(
&state.metrics.appflowy_web_metrics,
server,
user,
state.collab_access_control_storage.clone(),
&state.pg_pool,
workspace_uuid,
view_id,
&suffix,
)
.await
.unwrap();
Ok(Json(AppResponse::Ok()))
}

async fn move_page_to_trash_handler(
user_uuid: UserUuid,
path: web::Path<(Uuid, String)>,
Expand Down
85 changes: 85 additions & 0 deletions src/biz/collab/database.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use std::sync::Arc;

use app_error::AppError;
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
use async_trait::async_trait;
use collab::preclude::Collab;
use collab_database::{
database::{gen_database_group_id, gen_field_id},
entity::FieldType,
error::DatabaseError,
fields::{
date_type_option::DateTypeOption, default_field_settings_for_fields,
select_type_option::SingleSelectTypeOption, Field, TypeOptionData,
Expand All @@ -10,7 +16,16 @@ use collab_database::{
BoardLayoutSetting, CalendarLayoutSetting, DatabaseLayout, FieldSettingsByFieldIdMap, Group,
GroupSetting, GroupSettingMap, LayoutSettings,
},
workspace_database::{
DatabaseCollabPersistenceService, DatabaseCollabService, EncodeCollabByOid,
},
};
use collab_entity::{CollabType, EncodedCollab};
use collab_folder::CollabOrigin;
use database::collab::GetCollabOrigin;
use uuid::Uuid;

use super::utils::{batch_get_latest_collab_encoded, get_latest_collab_encoded};

pub struct LinkedViewDependencies {
pub layout_settings: LayoutSettings,
Expand Down Expand Up @@ -154,6 +169,76 @@ fn create_card_status_field() -> Field {
.with_type_option_data(field_type, default_select_type_option.into())
}

#[derive(Clone)]
pub struct PostgresDatabaseCollabService {
pub workspace_id: Uuid,
pub collab_storage: Arc<CollabAccessControlStorage>,
}

impl PostgresDatabaseCollabService {
pub async fn get_collab(&self, oid: &str, collab_type: CollabType) -> EncodedCollab {
get_latest_collab_encoded(
&self.collab_storage,
GetCollabOrigin::Server,
&self.workspace_id.to_string(),
oid,
collab_type,
)
.await
.unwrap()
}
}

#[async_trait]
impl DatabaseCollabService for PostgresDatabaseCollabService {
async fn build_collab(
&self,
object_id: &str,
object_type: CollabType,
encoded_collab: Option<(EncodedCollab, bool)>,
) -> Result<Collab, DatabaseError> {
match encoded_collab {
None => Collab::new_with_source(
CollabOrigin::Empty,
object_id,
self.get_collab(object_id, object_type).await.into(),
vec![],
false,
)
.map_err(|err| DatabaseError::Internal(err.into())),
Some((encoded_collab, _)) => Collab::new_with_source(
CollabOrigin::Empty,
object_id,
encoded_collab.into(),
vec![],
false,
)
.map_err(|err| DatabaseError::Internal(err.into())),
}
}

async fn get_collabs(
&self,
object_ids: Vec<String>,
collab_type: CollabType,
) -> Result<EncodeCollabByOid, DatabaseError> {
let encoded_collabs = batch_get_latest_collab_encoded(
&self.collab_storage,
GetCollabOrigin::Server,
&self.workspace_id.to_string(),
&object_ids,
collab_type,
)
.await
.unwrap();
Ok(encoded_collabs)
}

fn persistence(&self) -> Option<Arc<dyn DatabaseCollabPersistenceService>> {
None
}
}

#[cfg(test)]
mod tests {
use collab_database::{
Expand Down
38 changes: 37 additions & 1 deletion src/biz/collab/folder_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use std::collections::HashSet;

use app_error::AppError;
use chrono::DateTime;
use collab_folder::{Folder, SectionItem, SpacePermission, ViewLayout as CollabFolderViewLayout};
use collab_folder::{
Folder, SectionItem, SpacePermission, View, ViewLayout as CollabFolderViewLayout,
};
use shared_entity::dto::workspace_dto::{
self, FavoriteFolderView, FolderView, FolderViewMinimal, RecentFolderView, TrashFolderView,
ViewLayout,
Expand Down Expand Up @@ -263,6 +265,40 @@ pub fn section_items_to_trash_folder_view(
.collect()
}

pub struct ViewTree {
pub view: View,
pub children: Vec<ViewTree>,
}

pub fn get_view_and_children(folder: &Folder, view_id: &str) -> Option<ViewTree> {
let private_space_and_trash_views = private_space_and_trash_view_ids(folder);
get_view_and_children_recursive(folder, &private_space_and_trash_views, view_id)
}

fn get_view_and_children_recursive(
folder: &Folder,
private_space_and_trash_views: &PrivateSpaceAndTrashViews,
view_id: &str,
) -> Option<ViewTree> {
if private_space_and_trash_views
.view_ids_in_trash
.contains(view_id)
{
return None;
}

folder.get_view(view_id).map(|view| ViewTree {
view: View::clone(&view),
children: view
.children
.iter()
.filter_map(|child_view_id| {
get_view_and_children_recursive(folder, private_space_and_trash_views, child_view_id)
})
.collect(),
})
}

pub fn check_if_view_ancestors_fulfil_condition(
view_id: &str,
collab_folder: &Folder,
Expand Down
Loading

0 comments on commit 8daa5b3

Please sign in to comment.