Merge pull request #931 from AppFlowy-IO/feat/custom-namespace-change-publish-name
feat: patching of publish name
This commit is contained in:
commit
f96f4f779e
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM public.af_workspace where workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0eae8461a1caa6a609bfc4f329a4768ca0372a7b8cac54d83e3277ea0ad5ed9d"
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE af_published_collab\n SET publish_name = $1\n WHERE workspace_id = $2\n AND view_id = $3\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2ba7fdabdd71d2e3ac6dc1b67af17e8dcb59d13b644bc336da5351184ec4c9e1"
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM public.af_workspace\n WHERE workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f58a2f05efbda0698d27d83be5c6816fc46e3de33f926c6343bcbfa90a387b07"
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ tonic-build = "0.11.0"
|
|||
log = "0.4.20"
|
||||
lettre = { version = "0.11.7", features = ["tokio1", "tokio1-native-tls"] }
|
||||
handlebars = "5.1.2"
|
||||
pin-project = "1.1.5"
|
||||
pin-project.workspace = true
|
||||
byteorder = "1.5.0"
|
||||
sha2 = "0.10.8"
|
||||
rayon.workspace = true
|
||||
|
|
@ -281,6 +281,7 @@ async_zip = { version = "0.0.17", features = ["full"] }
|
|||
sanitize-filename = "0.5.0"
|
||||
base64 = "0.22"
|
||||
md5 = "0.7.0"
|
||||
pin-project = "1.1.5"
|
||||
|
||||
# collaboration
|
||||
yrs = { version = "0.21.2", features = ["sync"] }
|
||||
|
|
|
|||
|
|
@ -357,6 +357,9 @@ pub enum ErrorCode {
|
|||
CustomNamespaceDisabled = 1044,
|
||||
CustomNamespaceDisallowed = 1045,
|
||||
TooManyImportTask = 1046,
|
||||
CustomNamespaceTooShort = 1047,
|
||||
CustomNamespaceTooLong = 1048,
|
||||
CustomNamespaceReserved = 1049,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ collab-rt-entity = { workspace = true }
|
|||
client-api-entity.workspace = true
|
||||
serde_urlencoded = "0.7.1"
|
||||
futures.workspace = true
|
||||
pin-project = "1.1.5"
|
||||
pin-project.workspace = true
|
||||
percent-encoding = "2.3.1"
|
||||
lazy_static = { workspace = true }
|
||||
mime_guess = "2.0.5"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ use client_api_entity::workspace_dto::PublishInfoView;
|
|||
use client_api_entity::{workspace_dto::PublishedDuplicate, PublishInfo, UpdatePublishNamespace};
|
||||
use client_api_entity::{
|
||||
CreateGlobalCommentParams, CreateReactionParams, DeleteGlobalCommentParams, DeleteReactionParams,
|
||||
GetReactionQueryParams, GlobalComments, PublishInfoMeta, Reactions, UpdateDefaultPublishView,
|
||||
GetReactionQueryParams, GlobalComments, PatchPublishedCollab, PublishInfoMeta, Reactions,
|
||||
UpdateDefaultPublishView,
|
||||
};
|
||||
use reqwest::Method;
|
||||
use shared_entity::response::{AppResponse, AppResponseError};
|
||||
|
|
@ -75,6 +76,22 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn patch_published_collabs(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
patches: &[PatchPublishedCollab],
|
||||
) -> Result<(), AppResponseError> {
|
||||
let url = format!("{}/api/workspace/{}/publish", self.base_url, workspace_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::PATCH, &url)
|
||||
.await?
|
||||
.json(patches)
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
pub async fn unpublish_collabs(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
|
|
@ -251,7 +268,7 @@ impl Client {
|
|||
&self,
|
||||
view_id: &uuid::Uuid,
|
||||
) -> Result<PublishInfo, AppResponseError> {
|
||||
let url = format!("{}/api/workspace/published-info/{}", self.base_url, view_id,);
|
||||
let url = format!("{}/api/workspace/published-info/{}", self.base_url, view_id);
|
||||
|
||||
let resp = self.cloud_client.get(&url).send().await?;
|
||||
AppResponse::<PublishInfo>::from_response(resp)
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,12 @@ pub struct PublishCollabItem<Meta, Data> {
|
|||
pub data: Data,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PatchPublishedCollab {
|
||||
pub view_id: Uuid,
|
||||
pub publish_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GlobalComments {
|
||||
pub comments: Vec<GlobalComment>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use app_error::AppError;
|
||||
use database_entity::dto::{PublishCollabItem, PublishCollabKey, PublishInfo};
|
||||
use database_entity::dto::{
|
||||
PatchPublishedCollab, PublishCollabItem, PublishCollabKey, PublishInfo,
|
||||
};
|
||||
use sqlx::{Executor, PgPool, Postgres};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -238,6 +240,46 @@ pub async fn delete_published_collabs<'a, E: Executor<'a, Database = Postgres>>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn update_published_collabs(
|
||||
txn: &mut sqlx::Transaction<'_, Postgres>,
|
||||
workspace_id: &Uuid,
|
||||
patches: &[PatchPublishedCollab],
|
||||
) -> Result<(), AppError> {
|
||||
for patch in patches {
|
||||
let new_publish_name = match &patch.publish_name {
|
||||
Some(new_publish_name) => new_publish_name,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
UPDATE af_published_collab
|
||||
SET publish_name = $1
|
||||
WHERE workspace_id = $2
|
||||
AND view_id = $3
|
||||
"#,
|
||||
patch.publish_name,
|
||||
workspace_id,
|
||||
patch.view_id,
|
||||
)
|
||||
.execute(txn.as_mut())
|
||||
.await?;
|
||||
|
||||
if res.rows_affected() != 1 {
|
||||
tracing::error!(
|
||||
"Failed to update published collab publish name, workspace_id: {}, view_id: {}, new_publish_name: {}, rows_affected: {}",
|
||||
workspace_id,
|
||||
patch.view_id,
|
||||
new_publish_name,
|
||||
res.rows_affected()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn select_published_metadata_for_view_id(
|
||||
pg_pool: &PgPool,
|
||||
|
|
|
|||
|
|
@ -19,16 +19,24 @@ use app_error::AppError;
|
|||
|
||||
#[inline]
|
||||
pub async fn delete_from_workspace(pg_pool: &PgPool, workspace_id: &Uuid) -> Result<(), AppError> {
|
||||
let pg_row = sqlx::query!(
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM public.af_workspace where workspace_id = $1
|
||||
DELETE FROM public.af_workspace
|
||||
WHERE workspace_id = $1
|
||||
"#,
|
||||
workspace_id
|
||||
)
|
||||
.execute(pg_pool)
|
||||
.await?;
|
||||
|
||||
debug_assert!(pg_row.rows_affected() == 1);
|
||||
if res.rows_affected() != 1 {
|
||||
tracing::error!(
|
||||
"Failed to delete workspace, workspace_id: {}, rows_affected: {}",
|
||||
workspace_id,
|
||||
res.rows_affected()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ serde_json.workspace = true
|
|||
tracing.workspace = true
|
||||
bytes = { workspace = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
pin-project = "1.1.6"
|
||||
pin-project.workspace = true
|
||||
futures = "0.3.30"
|
||||
|
||||
[features]
|
||||
file_util = ["tokio/fs"]
|
||||
request_util = ["reqwest"]
|
||||
request_util = ["reqwest"]
|
||||
|
|
|
|||
|
|
@ -204,7 +204,8 @@ pub fn workspace_scope() -> Scope {
|
|||
.service(
|
||||
web::resource("/{workspace_id}/publish")
|
||||
.route(web::post().to(post_publish_collabs_handler))
|
||||
.route(web::delete().to(delete_published_collabs_handler)),
|
||||
.route(web::delete().to(delete_published_collabs_handler))
|
||||
.route(web::patch().to(patch_published_collabs_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{workspace_id}/folder").route(web::get().to(get_workspace_folder_handler)),
|
||||
|
|
@ -1440,6 +1441,23 @@ async fn post_publish_collabs_handler(
|
|||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn patch_published_collabs_handler(
|
||||
workspace_id: web::Path<Uuid>,
|
||||
user_uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
patches: Json<Vec<PatchPublishedCollab>>,
|
||||
) -> Result<Json<AppResponse<()>>> {
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
if patches.is_empty() {
|
||||
return Err(AppError::InvalidRequest("No patches provided".to_string()).into());
|
||||
}
|
||||
state
|
||||
.published_collab_store
|
||||
.patch_collabs(&workspace_id, &user_uuid, &patches)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn delete_published_collabs_handler(
|
||||
workspace_id: web::Path<Uuid>,
|
||||
user_uuid: UserUuid,
|
||||
|
|
@ -1449,11 +1467,11 @@ async fn delete_published_collabs_handler(
|
|||
let workspace_id = workspace_id.into_inner();
|
||||
let view_ids = view_ids.into_inner();
|
||||
if view_ids.is_empty() {
|
||||
return Ok(Json(AppResponse::Ok()));
|
||||
return Err(AppError::InvalidRequest("No view_ids provided".to_string()).into());
|
||||
}
|
||||
state
|
||||
.published_collab_store
|
||||
.delete_collab(&workspace_id, &view_ids, &user_uuid)
|
||||
.delete_collabs(&workspace_id, &view_ids, &user_uuid)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ use database::{
|
|||
collab::GetCollabOrigin,
|
||||
publish::{
|
||||
select_all_published_collab_info, select_default_published_view_id,
|
||||
select_default_published_view_id_for_namespace, update_workspace_default_publish_view,
|
||||
select_default_published_view_id_for_namespace, update_published_collabs,
|
||||
update_workspace_default_publish_view,
|
||||
},
|
||||
};
|
||||
use database_entity::dto::PatchPublishedCollab;
|
||||
use std::sync::Arc;
|
||||
|
||||
use app_error::AppError;
|
||||
|
|
@ -251,12 +253,19 @@ pub trait PublishedCollabStore: Sync + Send + 'static {
|
|||
publish_name: &str,
|
||||
) -> Result<Vec<u8>, AppError>;
|
||||
|
||||
async fn delete_collab(
|
||||
async fn delete_collabs(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
view_ids: &[Uuid],
|
||||
user_uuid: &Uuid,
|
||||
) -> Result<(), AppError>;
|
||||
|
||||
async fn patch_collabs(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
user_uuid: &Uuid,
|
||||
patches: &[PatchPublishedCollab],
|
||||
) -> Result<(), AppError>;
|
||||
}
|
||||
|
||||
pub struct PublishedCollabPostgresStore {
|
||||
|
|
@ -351,7 +360,7 @@ impl PublishedCollabStore for PublishedCollabPostgresStore {
|
|||
result
|
||||
}
|
||||
|
||||
async fn delete_collab(
|
||||
async fn delete_collabs(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
view_ids: &[Uuid],
|
||||
|
|
@ -361,6 +370,15 @@ impl PublishedCollabStore for PublishedCollabPostgresStore {
|
|||
delete_published_collabs(&self.pg_pool, workspace_id, view_ids).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn patch_collabs(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
user_uuid: &Uuid,
|
||||
patches: &[PatchPublishedCollab],
|
||||
) -> Result<(), AppError> {
|
||||
patch_collabs(&self.pg_pool, workspace_id, user_uuid, patches).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PublishedCollabS3StoreWithPostgresFallback {
|
||||
|
|
@ -519,7 +537,7 @@ impl PublishedCollabStore for PublishedCollabS3StoreWithPostgresFallback {
|
|||
}
|
||||
}
|
||||
|
||||
async fn delete_collab(
|
||||
async fn delete_collabs(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
view_ids: &[Uuid],
|
||||
|
|
@ -534,4 +552,36 @@ impl PublishedCollabStore for PublishedCollabS3StoreWithPostgresFallback {
|
|||
delete_published_collabs(&self.pg_pool, workspace_id, view_ids).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn patch_collabs(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
user_uuid: &Uuid,
|
||||
patches: &[PatchPublishedCollab],
|
||||
) -> Result<(), AppError> {
|
||||
patch_collabs(&self.pg_pool, workspace_id, user_uuid, patches).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn patch_collabs(
|
||||
pg_pool: &PgPool,
|
||||
workspace_id: &Uuid,
|
||||
user_uuid: &Uuid,
|
||||
patches: &[PatchPublishedCollab],
|
||||
) -> Result<(), AppError> {
|
||||
let view_ids = patches
|
||||
.iter()
|
||||
.map(|patch| patch.view_id)
|
||||
.collect::<Vec<Uuid>>();
|
||||
for patch in patches {
|
||||
if let Some(new_publish_name) = patch.publish_name.as_deref() {
|
||||
check_collab_publish_name(new_publish_name)?;
|
||||
}
|
||||
}
|
||||
check_workspace_owner_or_publisher(pg_pool, user_uuid, workspace_id, &view_ids).await?;
|
||||
|
||||
let mut txn = pg_pool.begin().await?;
|
||||
update_published_collabs(&mut txn, workspace_id, patches).await?;
|
||||
txn.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use app_error::ErrorCode;
|
|||
use appflowy_cloud::biz::collab::folder_view::collab_folder_to_folder_view;
|
||||
use appflowy_cloud::biz::workspace::ops::collab_from_doc_state;
|
||||
use client_api::entity::{
|
||||
AFRole, GlobalComment, PublishCollabItem, PublishCollabMetadata, PublishInfoMeta,
|
||||
AFRole, GlobalComment, PatchPublishedCollab, PublishCollabItem, PublishCollabMetadata,
|
||||
PublishInfoMeta,
|
||||
};
|
||||
use client_api_test::TestClient;
|
||||
use client_api_test::{generate_unique_registered_user_client, localhost_client};
|
||||
|
|
@ -250,6 +251,56 @@ async fn test_publish_doc() {
|
|||
assert_eq!(default_info_meta.meta.title, "my_title_1");
|
||||
}
|
||||
|
||||
{
|
||||
let new_publish_name_1 = "new-publish-name-1".to_string();
|
||||
|
||||
// User change publish name
|
||||
c.patch_published_collabs(
|
||||
&workspace_id,
|
||||
&[PatchPublishedCollab {
|
||||
view_id: view_id_1,
|
||||
publish_name: Some(new_publish_name_1.to_string()),
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Guest now cannot access the collab using old publish name
|
||||
let guest_client = localhost_client();
|
||||
let err = guest_client
|
||||
.get_published_collab::<MyCustomMetadata>(&my_namespace, publish_name_1)
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound, "{:?}", err);
|
||||
|
||||
// Guest now access the collab using new publish name
|
||||
let guest_client = localhost_client();
|
||||
let _ = guest_client
|
||||
.get_published_collab::<MyCustomMetadata>(&my_namespace, &new_publish_name_1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Switch back to old publish name
|
||||
c.patch_published_collabs(
|
||||
&workspace_id,
|
||||
&[PatchPublishedCollab {
|
||||
view_id: view_id_1,
|
||||
publish_name: Some(publish_name_1.to_string()),
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Guest can access the collab using the orginal publish name
|
||||
let guest_client = localhost_client();
|
||||
let _ = guest_client
|
||||
.get_published_collab::<MyCustomMetadata>(&my_namespace, publish_name_1)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// user unpublish collabs
|
||||
c.unpublish_collabs(&workspace_id, &[view_id_1, view_id_2])
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
|||
Loading…
Reference in New Issue