Merge pull request #669 from AppFlowy-IO/fix/publish

fix: fix various publish issues
This commit is contained in:
Zack 2024-06-30 00:09:05 +08:00 committed by GitHub
commit a4d340bff1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 124 additions and 24 deletions

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO af_published_collab (workspace_id, view_id, publish_name, published_by, metadata, blob)\n SELECT * FROM UNNEST(\n (SELECT array_agg((SELECT $1::uuid)) FROM generate_series(1, $7))::uuid[],\n $2::uuid[],\n $3::text[],\n (SELECT array_agg((SELECT uid FROM af_user WHERE uuid = $4)) FROM generate_series(1, $7))::bigint[],\n $5::jsonb[],\n $6::bytea[]\n )\n ON CONFLICT (workspace_id, view_id) DO UPDATE\n SET metadata = EXCLUDED.metadata\n ",
"query": "\n INSERT INTO af_published_collab (workspace_id, view_id, publish_name, published_by, metadata, blob)\n SELECT * FROM UNNEST(\n (SELECT array_agg((SELECT $1::uuid)) FROM generate_series(1, $7))::uuid[],\n $2::uuid[],\n $3::text[],\n (SELECT array_agg((SELECT uid FROM af_user WHERE uuid = $4)) FROM generate_series(1, $7))::bigint[],\n $5::jsonb[],\n $6::bytea[]\n )\n ON CONFLICT (workspace_id, view_id) DO UPDATE\n SET metadata = EXCLUDED.metadata,\n blob = EXCLUDED.blob,\n published_by = EXCLUDED.published_by,\n publish_name = EXCLUDED.publish_name\n ",
"describe": {
"columns": [],
"parameters": {
@ -16,5 +16,5 @@
},
"nullable": []
},
"hash": "3ad29cddcb72f375c98db91f697cbe957267575acd14ec01af2425617eea5628"
"hash": "0857da7f3d100186aab8a6f881dfec948a99c96d6f02bcf11eae7aeeea62e5a0"
}

View File

@ -15,7 +15,7 @@
]
},
"nullable": [
true
false
]
},
"hash": "0d4b11e74a5a9835c82a2ea8f87499d6cdd570979d0c3e001fa40e140a64fedf"

View File

@ -117,9 +117,6 @@ pub enum AppError {
#[error("{0}")]
OverrideWithIncorrectData(String),
#[error("{0}")]
PublishNamespaceNotSet(String),
#[error("{0}")]
PublishNamespaceAlreadyTaken(String),
}
@ -180,7 +177,6 @@ impl AppError {
AppError::NoRequiredData(_) => ErrorCode::NoRequiredData,
AppError::OverrideWithIncorrectData(_) => ErrorCode::OverrideWithIncorrectData,
AppError::Utf8Error(_) => ErrorCode::Internal,
AppError::PublishNamespaceNotSet(_) => ErrorCode::PublishNamespaceNotSet,
AppError::PublishNamespaceAlreadyTaken(_) => ErrorCode::PublishNamespaceAlreadyTaken,
}
}

View File

@ -887,7 +887,7 @@ pub async fn update_workspace_publish_namespace<'a, E: Executor<'a, Database = P
pub async fn select_workspace_publish_namespace<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: &Uuid,
) -> Result<Option<String>, AppError> {
) -> Result<String, AppError> {
let res = sqlx::query_scalar!(
r#"
SELECT publish_namespace
@ -932,7 +932,10 @@ pub async fn insert_or_replace_publish_collab_metas<'a, E: Executor<'a, Database
$6::bytea[]
)
ON CONFLICT (workspace_id, view_id) DO UPDATE
SET metadata = EXCLUDED.metadata
SET metadata = EXCLUDED.metadata,
blob = EXCLUDED.blob,
published_by = EXCLUDED.published_by,
publish_name = EXCLUDED.publish_name
"#,
workspace_id,
&view_ids,

View File

@ -0,0 +1,9 @@
-- Update existing null values to ensure no nulls are present before adding NOT NULL constraint
UPDATE public.af_workspace
SET publish_namespace = uuid_generate_v4()::text
WHERE publish_namespace IS NULL;
-- Alter the column to set NOT NULL constraint and a default value
ALTER TABLE public.af_workspace
ALTER COLUMN publish_namespace SET NOT NULL,
ALTER COLUMN publish_namespace SET DEFAULT uuid_generate_v4()::text;

View File

@ -47,6 +47,7 @@ pub const WORKSPACE_MEMBER_PATTERN: &str = "/api/workspace/{workspace_id}/member
pub const WORKSPACE_INVITE_PATTERN: &str = "/api/workspace/{workspace_id}/invite";
pub const COLLAB_PATTERN: &str = "/api/workspace/{workspace_id}/collab/{object_id}";
pub const V1_COLLAB_PATTERN: &str = "/api/workspace/v1/{workspace_id}/collab/{object_id}";
pub const WORKSPACE_PUBLISH_PATTERN: &str = "/api/workspace/{workspace_id}/publish";
pub const WORKSPACE_PUBLISH_NAMESPACE_PATTERN: &str =
"/api/workspace/{workspace_id}/publish-namespace";

View File

@ -21,7 +21,7 @@ use database_entity::dto::AFRole;
use crate::api::workspace::{
WORKSPACE_INVITE_PATTERN, WORKSPACE_MEMBER_PATTERN, WORKSPACE_PATTERN,
WORKSPACE_PUBLISH_NAMESPACE_PATTERN,
WORKSPACE_PUBLISH_NAMESPACE_PATTERN, WORKSPACE_PUBLISH_PATTERN,
};
use crate::middleware::access_control_mw::{AccessResource, MiddlewareAccessControl};
use crate::state::UserCache;
@ -48,6 +48,10 @@ where
],
// Require role for given resources
require_role_rules: vec![
(
ResourceDef::new(WORKSPACE_PUBLISH_PATTERN),
[(Method::DELETE, AFRole::Member)].into(),
),
// Only the Owner can manager the workspace members
(
ResourceDef::new(WORKSPACE_MEMBER_PATTERN),

View File

@ -132,15 +132,7 @@ pub async fn get_workspace_publish_namespace(
pg_pool: &PgPool,
workspace_id: &Uuid,
) -> Result<String, AppError> {
let namespace = match select_workspace_publish_namespace(pg_pool, workspace_id).await? {
Some(namespace) => namespace,
None => {
return Err(AppError::PublishNamespaceNotSet(
"publish namespace is not set for the workspace".to_string(),
))
},
};
Ok(namespace)
select_workspace_publish_namespace(pg_pool, workspace_id).await
}
pub async fn publish_collabs(

View File

@ -1,4 +1,5 @@
use client_api::entity::{PublishCollabItem, PublishCollabMetadata};
use client_api::entity::{AFRole, PublishCollabItem, PublishCollabMetadata};
use client_api_test::TestClient;
use client_api_test::{generate_unique_registered_user_client, localhost_client};
#[tokio::test]
@ -7,13 +8,11 @@ async fn test_set_publish_namespace_set() {
let workspace_id = get_first_workspace_string(&c).await;
{
// cannot get namespace if not set
let err = c
// can get namespace before setting, which is random string
let _ = c
.get_workspace_publish_namespace(&workspace_id.to_string())
.await
.err()
.unwrap();
assert_eq!(format!("{:?}", err.code), "PublishNamespaceNotSet");
}
let namespace = uuid::Uuid::new_v4().to_string();
@ -127,6 +126,65 @@ async fn test_publish_doc() {
.await
.unwrap();
assert_eq!(blob, "yrs_encoded_data_1");
let publish_info = guest_client
.get_published_collab_info(&view_id_2)
.await
.unwrap();
assert_eq!(publish_info.namespace, Some(my_namespace.clone()));
assert_eq!(publish_info.publish_name, publish_name_2);
assert_eq!(publish_info.view_id, view_id_2);
let blob = guest_client
.get_published_collab_blob(&my_namespace, publish_name_2)
.await
.unwrap();
assert_eq!(blob, "yrs_encoded_data_2");
}
// updates data
c.publish_collabs::<MyCustomMetadata, &[u8]>(
&workspace_id,
vec![
PublishCollabItem {
meta: PublishCollabMetadata {
view_id: view_id_1,
publish_name: publish_name_1.to_string(),
metadata: MyCustomMetadata {
title: "my_title_1".to_string(),
},
},
data: "yrs_encoded_data_3".as_bytes(),
},
PublishCollabItem {
meta: PublishCollabMetadata {
view_id: view_id_2,
publish_name: publish_name_2.to_string(),
metadata: MyCustomMetadata {
title: "my_title_2".to_string(),
},
},
data: "yrs_encoded_data_4".as_bytes(),
},
],
)
.await
.unwrap();
{
// should see updated data
let guest_client = localhost_client();
let blob = guest_client
.get_published_collab_blob(&my_namespace, publish_name_1)
.await
.unwrap();
assert_eq!(blob, "yrs_encoded_data_3");
let blob = guest_client
.get_published_collab_blob(&my_namespace, publish_name_2)
.await
.unwrap();
assert_eq!(blob, "yrs_encoded_data_4");
}
c.unpublish_collabs(&workspace_id, &[view_id_1])
@ -200,6 +258,43 @@ async fn get_first_workspace_string(c: &client_api::Client) -> String {
.to_string()
}
#[tokio::test]
async fn workspace_member_publish_unpublish() {
let client_1 = TestClient::new_user_without_ws_conn().await;
let workspace_id = client_1.workspace_id().await;
let client_2 = TestClient::new_user_without_ws_conn().await;
client_1
.invite_and_accepted_workspace_member(&workspace_id, &client_2, AFRole::Member)
.await
.unwrap();
let view_id = uuid::Uuid::new_v4();
// member can publish without owner setting namespace
client_2
.api_client
.publish_collabs::<MyCustomMetadata, &[u8]>(
&workspace_id,
vec![PublishCollabItem {
meta: PublishCollabMetadata {
view_id,
publish_name: "publish-name-1".to_string(),
metadata: MyCustomMetadata {
title: "my_title_1".to_string(),
},
},
data: "yrs_encoded_data_1".as_bytes(),
}],
)
.await
.unwrap();
client_2
.api_client
.unpublish_collabs(&workspace_id, &[view_id])
.await
.unwrap();
}
#[derive(serde::Serialize, serde::Deserialize)]
struct MyCustomMetadata {
title: String,