mirror of
https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
synced 2025-04-19 03:24:42 -04:00
feat: CRUD interface for custom namespace (#882)
* feat: listing all published_info * fix: add sqlx files * feat: add additional fields for publish info * feat: get and set default publish info * chore: cargo sqlx prepare * fix: cargo clippy * fix: test case exe order * chore: cargo sqlx * feat: get info and meta from workspace namespace * chore: cargo sqlx * feat: add original doc info for published view * chore: log all publish endpoints * fix: default values for publish info extra fields * feat: move namespace restriction to gateway
This commit is contained in:
parent
71fdace975
commit
60c589bd9c
24 changed files with 708 additions and 185 deletions
15
.sqlx/query-0781735c56d22370302beec06863dccbbb9e664b212de93e5073508a82b91609.json
generated
Normal file
15
.sqlx/query-0781735c56d22370302beec06863dccbbb9e664b212de93e5073508a82b91609.json
generated
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE af_workspace\n SET default_published_view_id = $1\n WHERE workspace_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0781735c56d22370302beec06863dccbbb9e664b212de93e5073508a82b91609"
|
||||
}
|
46
.sqlx/query-3ab817803745c1dc20eef819d944b8cdf56df3cd31b3ed8bc15e51eacace8e04.json
generated
Normal file
46
.sqlx/query-3ab817803745c1dc20eef819d944b8cdf56df3cd31b3ed8bc15e51eacace8e04.json
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n aw.publish_namespace AS namespace,\n apc.publish_name,\n apc.view_id,\n au.email AS publisher_email,\n apc.created_at AS publish_timestamp\n FROM af_published_collab apc\n JOIN af_user au ON apc.published_by = au.uid\n JOIN af_workspace aw ON apc.workspace_id = aw.workspace_id\n WHERE apc.workspace_id = $1;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "namespace",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "publish_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "view_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "publisher_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "publish_timestamp",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3ab817803745c1dc20eef819d944b8cdf56df3cd31b3ed8bc15e51eacace8e04"
|
||||
}
|
46
.sqlx/query-b2724ac4427c488c23e645c1aaf3c1e9b23e8042abb3ad4e255533dda39f6309.json
generated
Normal file
46
.sqlx/query-b2724ac4427c488c23e645c1aaf3c1e9b23e8042abb3ad4e255533dda39f6309.json
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n aw.publish_namespace AS namespace,\n apc.publish_name,\n apc.view_id,\n au.email AS publisher_email,\n apc.created_at AS publish_timestamp\n FROM af_published_collab apc\n JOIN af_user au ON apc.published_by = au.uid\n JOIN af_workspace aw ON apc.workspace_id = aw.workspace_id\n WHERE apc.view_id = $1;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "namespace",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "publish_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "view_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "publisher_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "publish_timestamp",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b2724ac4427c488c23e645c1aaf3c1e9b23e8042abb3ad4e255533dda39f6309"
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n aw.publish_namespace AS namespace,\n apc.publish_name,\n apc.view_id\n FROM af_published_collab apc\n LEFT JOIN af_workspace aw\n ON apc.workspace_id = aw.workspace_id\n WHERE apc.view_id = $1;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "namespace",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "publish_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "view_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bc5df5a1fe64ed4f32654f09d0d62459d02f494912fb38b97f87c46b62a69b1f"
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n (SELECT publish_namespace FROM af_workspace aw WHERE aw.workspace_id = apc.workspace_id) AS namespace,\n publish_name,\n view_id\n FROM af_published_collab apc\n WHERE view_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "namespace",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "publish_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "view_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f8e631002ccbe616ab558a2025d892f7812dfa039276534664f60b37e3898c75"
|
||||
}
|
22
.sqlx/query-f9c28d0fa124ef543259c6869d7c517deabda3af9a67c6e59d8e15c0245c83a0.json
generated
Normal file
22
.sqlx/query-f9c28d0fa124ef543259c6869d7c517deabda3af9a67c6e59d8e15c0245c83a0.json
generated
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT default_published_view_id\n FROM af_workspace\n WHERE workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "default_published_view_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f9c28d0fa124ef543259c6869d7c517deabda3af9a67c6e59d8e15c0245c83a0"
|
||||
}
|
22
.sqlx/query-ff0397184dd291eed259ca8518a5c1541d0971f95e51b859dd0ce02702eacd66.json
generated
Normal file
22
.sqlx/query-ff0397184dd291eed259ca8518a5c1541d0971f95e51b859dd0ce02702eacd66.json
generated
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT default_published_view_id\n FROM af_workspace\n WHERE publish_namespace = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "default_published_view_id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ff0397184dd291eed259ca8518a5c1541d0971f95e51b859dd0ce02702eacd66"
|
||||
}
|
|
@ -350,6 +350,8 @@ pub enum ErrorCode {
|
|||
NotInviteeOfWorkspaceInvitation = 1041,
|
||||
MissingView = 1042,
|
||||
AccessRequestAlreadyExists = 1043,
|
||||
CustomNamespaceDisabled = 1044,
|
||||
CustomNamespaceDisallowed = 1045,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
|
|
@ -1,17 +1,39 @@
|
|||
use bytes::Bytes;
|
||||
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, Reactions,
|
||||
GetReactionQueryParams, GlobalComments, PublishInfoMeta, Reactions, UpdateDefaultPublishView,
|
||||
};
|
||||
use reqwest::Method;
|
||||
use shared_entity::response::{AppResponse, AppResponseError};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::Client;
|
||||
use crate::{log_request_id, Client};
|
||||
|
||||
// Publisher API
|
||||
impl Client {
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn list_published_views(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<PublishInfoView>, AppResponseError> {
|
||||
let url = format!(
|
||||
"{}/api/workspace/{}/published-info",
|
||||
self.base_url, workspace_id,
|
||||
);
|
||||
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<Vec<PublishInfoView>>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn set_workspace_publish_namespace(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
|
@ -30,7 +52,7 @@ impl Client {
|
|||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
|
@ -47,6 +69,7 @@ impl Client {
|
|||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<String>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
|
@ -64,6 +87,7 @@ impl Client {
|
|||
.json(view_ids)
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
|
@ -86,6 +110,7 @@ impl Client {
|
|||
})
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
|
@ -106,6 +131,7 @@ impl Client {
|
|||
})
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
|
@ -128,6 +154,7 @@ impl Client {
|
|||
})
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
|
@ -150,8 +177,47 @@ impl Client {
|
|||
})
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
pub async fn set_default_publish_view(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
view_id: uuid::Uuid,
|
||||
) -> Result<(), AppResponseError> {
|
||||
let url = format!(
|
||||
"{}/api/workspace/{}/publish-default",
|
||||
self.base_url, workspace_id
|
||||
);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::PUT, &url)
|
||||
.await?
|
||||
.json(&UpdateDefaultPublishView { view_id })
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
pub async fn get_default_publish_view_info(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> Result<PublishInfo, AppResponseError> {
|
||||
let url = format!(
|
||||
"{}/api/workspace/{}/publish-default",
|
||||
self.base_url, workspace_id
|
||||
);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<PublishInfo>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
}
|
||||
|
||||
// Optional login
|
||||
|
@ -171,6 +237,7 @@ impl Client {
|
|||
};
|
||||
|
||||
let resp = client.send().await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<GlobalComments>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
|
@ -192,6 +259,32 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_default_published_collab<T>(
|
||||
&self,
|
||||
publish_namespace: &str,
|
||||
) -> Result<PublishInfoMeta<T>, AppResponseError>
|
||||
where
|
||||
T: serde::de::DeserializeOwned + 'static,
|
||||
{
|
||||
let url = format!(
|
||||
"{}/api/workspace/published/{}",
|
||||
self.base_url, publish_namespace,
|
||||
);
|
||||
|
||||
let resp = self
|
||||
.cloud_client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
|
||||
log_request_id(&resp);
|
||||
AppResponse::<PublishInfoMeta<T>>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_published_collab<T>(
|
||||
&self,
|
||||
|
@ -217,6 +310,7 @@ impl Client {
|
|||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
log_request_id(&resp);
|
||||
|
||||
let txt = resp.text().await?;
|
||||
|
||||
|
@ -244,14 +338,9 @@ impl Client {
|
|||
"{}/api/workspace/published/{}/{}/blob",
|
||||
self.base_url, publish_namespace, publish_name
|
||||
);
|
||||
let bytes = self
|
||||
.cloud_client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.bytes()
|
||||
.await?;
|
||||
let resp = self.cloud_client.get(&url).send().await?;
|
||||
log_request_id(&resp);
|
||||
let bytes = resp.error_for_status()?.bytes().await?;
|
||||
|
||||
if let Ok(app_err) = serde_json::from_slice::<AppResponseError>(&bytes) {
|
||||
return Err(app_err);
|
||||
|
@ -275,6 +364,7 @@ impl Client {
|
|||
.json(publish_duplicate)
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
|
@ -295,6 +385,7 @@ impl Client {
|
|||
})
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
AppResponse::<Reactions>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
|
|
|
@ -374,6 +374,17 @@ pub struct UpdatePublishNamespace {
|
|||
pub new_namespace: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UpdateDefaultPublishView {
|
||||
pub view_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DefaultPublishViewInfoMeta {
|
||||
pub info: PublishInfo,
|
||||
pub meta: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
||||
pub struct QueryCollabMembers {
|
||||
#[validate(custom = "validate_not_empty_str")]
|
||||
|
@ -397,11 +408,21 @@ pub struct AFCollabMember {
|
|||
pub permission: AFPermission,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PublishInfo {
|
||||
pub namespace: Option<String>,
|
||||
pub publish_name: String,
|
||||
pub view_id: Uuid,
|
||||
#[serde(default)]
|
||||
pub publisher_email: String,
|
||||
#[serde(default)]
|
||||
pub publish_timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PublishInfoMeta<Meta> {
|
||||
pub info: PublishInfo,
|
||||
pub meta: Meta,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)]
|
||||
|
|
|
@ -82,6 +82,34 @@ pub async fn update_workspace_publish_namespace<'a, E: Executor<'a, Database = P
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn update_workspace_default_publish_view<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
workspace_id: &Uuid,
|
||||
new_view_id: &Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
UPDATE af_workspace
|
||||
SET default_published_view_id = $1
|
||||
WHERE workspace_id = $2
|
||||
"#,
|
||||
new_view_id,
|
||||
workspace_id,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
if res.rows_affected() != 1 {
|
||||
tracing::error!(
|
||||
"Failed to update workspace default publish view, workspace_id: {}, new_view_id: {}, rows_affected: {}",
|
||||
workspace_id, new_view_id, res.rows_affected()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn select_workspace_publish_namespace<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
|
@ -290,6 +318,45 @@ pub async fn select_published_collab_blob<'a, E: Executor<'a, Database = Postgre
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_default_published_view_id_for_namespace<
|
||||
'a,
|
||||
E: Executor<'a, Database = Postgres>,
|
||||
>(
|
||||
executor: E,
|
||||
namespace: &str,
|
||||
) -> Result<Option<Uuid>, AppError> {
|
||||
let res = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT default_published_view_id
|
||||
FROM af_workspace
|
||||
WHERE publish_namespace = $1
|
||||
"#,
|
||||
namespace,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_default_published_view_id<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<Option<Uuid>, AppError> {
|
||||
let res = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT default_published_view_id
|
||||
FROM af_workspace
|
||||
WHERE workspace_id = $1
|
||||
"#,
|
||||
workspace_id,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_published_collab_info<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: &Uuid,
|
||||
|
@ -300,10 +367,12 @@ pub async fn select_published_collab_info<'a, E: Executor<'a, Database = Postgre
|
|||
SELECT
|
||||
aw.publish_namespace AS namespace,
|
||||
apc.publish_name,
|
||||
apc.view_id
|
||||
apc.view_id,
|
||||
au.email AS publisher_email,
|
||||
apc.created_at AS publish_timestamp
|
||||
FROM af_published_collab apc
|
||||
LEFT JOIN af_workspace aw
|
||||
ON apc.workspace_id = aw.workspace_id
|
||||
JOIN af_user au ON apc.published_by = au.uid
|
||||
JOIN af_workspace aw ON apc.workspace_id = aw.workspace_id
|
||||
WHERE apc.view_id = $1;
|
||||
"#,
|
||||
view_id,
|
||||
|
@ -314,6 +383,32 @@ pub async fn select_published_collab_info<'a, E: Executor<'a, Database = Postgre
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_all_published_collab_info<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<Vec<PublishInfo>, AppError> {
|
||||
let res = sqlx::query_as!(
|
||||
PublishInfo,
|
||||
r#"
|
||||
SELECT
|
||||
aw.publish_namespace AS namespace,
|
||||
apc.publish_name,
|
||||
apc.view_id,
|
||||
au.email AS publisher_email,
|
||||
apc.created_at AS publish_timestamp
|
||||
FROM af_published_collab apc
|
||||
JOIN af_user au ON apc.published_by = au.uid
|
||||
JOIN af_workspace aw ON apc.workspace_id = aw.workspace_id
|
||||
WHERE apc.workspace_id = $1;
|
||||
"#,
|
||||
workspace_id,
|
||||
)
|
||||
.fetch_all(executor)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_workspace_id_for_publish_namespace<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
publish_namespace: &str,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use database_entity::dto::{
|
||||
AFRole, AFWorkspaceInvitation, AFWorkspaceInvitationStatus, AFWorkspaceSettings, GlobalComment,
|
||||
PublishInfo, Reaction,
|
||||
Reaction,
|
||||
};
|
||||
use futures_util::stream::BoxStream;
|
||||
use sqlx::{types::uuid, Executor, PgPool, Postgres, Transaction};
|
||||
|
@ -1190,28 +1190,6 @@ pub async fn select_published_collab_blob<'a, E: Executor<'a, Database = Postgre
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_published_collab_info<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: &Uuid,
|
||||
) -> Result<PublishInfo, AppError> {
|
||||
let res = sqlx::query_as!(
|
||||
PublishInfo,
|
||||
r#"
|
||||
SELECT
|
||||
(SELECT publish_namespace FROM af_workspace aw WHERE aw.workspace_id = apc.workspace_id) AS namespace,
|
||||
publish_name,
|
||||
view_id
|
||||
FROM af_published_collab apc
|
||||
WHERE view_id = $1
|
||||
"#,
|
||||
view_id,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_owner_of_published_collab<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: &Uuid,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use collab_entity::{CollabType, EncodedCollab};
|
||||
use database_entity::dto::{AFRole, AFWebUser, AFWorkspaceInvitationStatus};
|
||||
use database_entity::dto::{AFRole, AFWebUser, AFWorkspaceInvitationStatus, PublishInfo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::{collections::HashMap, ops::Deref};
|
||||
|
@ -166,6 +166,13 @@ pub struct FolderViewMinimal {
|
|||
pub layout: ViewLayout,
|
||||
}
|
||||
|
||||
/// Publish info with actual view info
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PublishInfoView {
|
||||
pub view: FolderViewMinimal,
|
||||
pub info: PublishInfo,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SectionItems {
|
||||
pub views: Vec<FolderView>,
|
||||
|
|
4
migrations/20241014153023_default_published_view.sql
Normal file
4
migrations/20241014153023_default_published_view.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE af_workspace ADD COLUMN default_published_view_id UUID;
|
||||
|
||||
ALTER TABLE af_published_collab ALTER COLUMN created_at SET NOT NULL;
|
||||
ALTER TABLE af_published_collab ALTER COLUMN updated_at SET NOT NULL;
|
|
@ -40,7 +40,7 @@ async fn get_access_request_handler(
|
|||
let uid = state.user_cache.get_user_uid(&uuid).await?;
|
||||
let access_request = get_access_request(
|
||||
&state.pg_pool,
|
||||
state.collab_access_control_storage.clone(),
|
||||
&state.collab_access_control_storage,
|
||||
access_request_id,
|
||||
uid,
|
||||
)
|
||||
|
|
|
@ -49,6 +49,7 @@ use crate::biz::workspace::ops::{
|
|||
get_reactions_on_published_view, remove_comment_on_published_view, remove_reaction_on_comment,
|
||||
};
|
||||
use crate::biz::workspace::page_view::{get_page_view_collab, update_page_collab_data};
|
||||
use crate::biz::workspace::publish::get_workspace_default_publish_view_info_meta;
|
||||
use crate::domain::compression::{
|
||||
blocking_decompress, decompress, CompressionType, X_COMPRESSION_TYPE,
|
||||
};
|
||||
|
@ -150,6 +151,10 @@ pub fn workspace_scope() -> Scope {
|
|||
.route(web::put().to(update_collab_member_handler))
|
||||
.route(web::delete().to(remove_collab_member_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/published/{publish_namespace}")
|
||||
.route(web::get().to(get_default_published_collab_info_meta_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/published/{publish_namespace}/{publish_name}")
|
||||
.route(web::get().to(get_published_collab_handler)),
|
||||
|
@ -162,6 +167,10 @@ pub fn workspace_scope() -> Scope {
|
|||
web::resource("{workspace_id}/published-duplicate")
|
||||
.route(web::post().to(post_published_duplicate_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{workspace_id}/published-info")
|
||||
.route(web::get().to(list_published_collab_info_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/published-info/{view_id}")
|
||||
.route(web::get().to(get_published_collab_info_handler)),
|
||||
|
@ -183,6 +192,11 @@ pub fn workspace_scope() -> Scope {
|
|||
.route(web::put().to(put_publish_namespace_handler))
|
||||
.route(web::get().to(get_publish_namespace_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{workspace_id}/publish-default")
|
||||
.route(web::put().to(put_workspace_default_published_view_handler))
|
||||
.route(web::get().to(get_workspace_published_default_info_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{workspace_id}/publish")
|
||||
.route(web::post().to(post_publish_collabs_handler))
|
||||
|
@ -1136,6 +1150,34 @@ async fn remove_collab_member_handler(
|
|||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn put_workspace_default_published_view_handler(
|
||||
user_uuid: UserUuid,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
payload: Json<UpdateDefaultPublishView>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<()>>> {
|
||||
let new_default_pub_view_id = payload.into_inner().view_id;
|
||||
biz::workspace::publish::set_workspace_default_publish_view(
|
||||
&state.pg_pool,
|
||||
&user_uuid,
|
||||
&workspace_id,
|
||||
&new_default_pub_view_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn get_workspace_published_default_info_handler(
|
||||
workspace_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<PublishInfo>>> {
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let info =
|
||||
biz::workspace::publish::get_workspace_default_publish_view_info(&state.pg_pool, &workspace_id)
|
||||
.await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(info)))
|
||||
}
|
||||
|
||||
async fn put_publish_namespace_handler(
|
||||
user_uuid: UserUuid,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
|
@ -1164,6 +1206,18 @@ async fn get_publish_namespace_handler(
|
|||
Ok(Json(AppResponse::Ok().with_data(namespace)))
|
||||
}
|
||||
|
||||
async fn get_default_published_collab_info_meta_handler(
|
||||
publish_namespace: web::Path<String>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<PublishInfoMeta<serde_json::Value>>>> {
|
||||
let publish_namespace = publish_namespace.into_inner();
|
||||
let (info, meta) =
|
||||
get_workspace_default_publish_view_info_meta(&state.pg_pool, &publish_namespace).await?;
|
||||
Ok(Json(
|
||||
AppResponse::Ok().with_data(PublishInfoMeta { info, meta }),
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_published_collab_handler(
|
||||
path_param: web::Path<(String, String)>,
|
||||
state: Data<AppState>,
|
||||
|
@ -1209,6 +1263,20 @@ async fn post_published_duplicate_handler(
|
|||
Ok(Json(AppResponse::Ok()))
|
||||
}
|
||||
|
||||
async fn list_published_collab_info_handler(
|
||||
workspace_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<Vec<PublishInfoView>>>> {
|
||||
let publish_infos = biz::workspace::publish::list_collab_publish_info(
|
||||
state.published_collab_store.as_ref(),
|
||||
&state.collab_access_control_storage,
|
||||
&workspace_id.into_inner(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(AppResponse::Ok().with_data(publish_infos)))
|
||||
}
|
||||
|
||||
async fn get_published_collab_info_handler(
|
||||
view_id: web::Path<Uuid>,
|
||||
state: Data<AppState>,
|
||||
|
@ -1478,7 +1546,7 @@ async fn get_workspace_folder_handler(
|
|||
workspace_id.to_string()
|
||||
};
|
||||
let folder_view = biz::collab::ops::get_user_workspace_structure(
|
||||
state.collab_access_control_storage.clone(),
|
||||
&state.collab_access_control_storage,
|
||||
&state.pg_pool,
|
||||
uid,
|
||||
workspace_id,
|
||||
|
@ -1497,7 +1565,7 @@ async fn get_recent_views_handler(
|
|||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let folder_views = get_user_recent_folder_views(
|
||||
state.collab_access_control_storage.clone(),
|
||||
&state.collab_access_control_storage,
|
||||
&state.pg_pool,
|
||||
uid,
|
||||
workspace_id,
|
||||
|
@ -1517,7 +1585,7 @@ async fn get_favorite_views_handler(
|
|||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let folder_views = get_user_favorite_folder_views(
|
||||
state.collab_access_control_storage.clone(),
|
||||
&state.collab_access_control_storage,
|
||||
&state.pg_pool,
|
||||
uid,
|
||||
workspace_id,
|
||||
|
@ -1536,12 +1604,8 @@ async fn get_trash_views_handler(
|
|||
) -> Result<Json<AppResponse<SectionItems>>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let folder_views = get_user_trash_folder_views(
|
||||
state.collab_access_control_storage.clone(),
|
||||
uid,
|
||||
workspace_id,
|
||||
)
|
||||
.await?;
|
||||
let folder_views =
|
||||
get_user_trash_folder_views(&state.collab_access_control_storage, uid, workspace_id).await?;
|
||||
let section_items = SectionItems {
|
||||
views: folder_views,
|
||||
};
|
||||
|
@ -1553,7 +1617,7 @@ async fn get_workspace_publish_outline_handler(
|
|||
state: Data<AppState>,
|
||||
) -> Result<Json<AppResponse<PublishedView>>> {
|
||||
let published_view = biz::collab::ops::get_published_view(
|
||||
state.collab_access_control_storage.clone(),
|
||||
&state.collab_access_control_storage,
|
||||
publish_namespace.into_inner(),
|
||||
&state.pg_pool,
|
||||
)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::{ops::DerefMut, sync::Arc};
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::mailer::AFCloudMailer;
|
||||
use crate::{
|
||||
biz::collab::{
|
||||
folder_view::{to_dto_view_icon, to_view_layout},
|
||||
folder_view::{to_dto_view_icon, to_dto_view_layout},
|
||||
ops::get_latest_collab_folder,
|
||||
},
|
||||
mailer::{WorkspaceAccessRequestApprovedMailerParam, WorkspaceAccessRequestMailerParam},
|
||||
|
@ -72,7 +73,7 @@ pub async fn create_access_request(
|
|||
|
||||
pub async fn get_access_request(
|
||||
pg_pool: &PgPool,
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
access_request_id: Uuid,
|
||||
user_uid: i64,
|
||||
) -> Result<AccessRequest, AppError> {
|
||||
|
@ -96,7 +97,7 @@ pub async fn get_access_request(
|
|||
view_id: v.id.clone(),
|
||||
name: v.name.clone(),
|
||||
icon: v.icon.as_ref().map(|icon| to_dto_view_icon(icon.clone())),
|
||||
layout: to_view_layout(&v.layout),
|
||||
layout: to_dto_view_layout(&v.layout),
|
||||
})
|
||||
.ok_or(AppError::MissingView(format!(
|
||||
"the view {} is missing",
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::collections::HashSet;
|
|||
use app_error::AppError;
|
||||
use chrono::DateTime;
|
||||
use collab_folder::{Folder, SectionItem, ViewLayout as CollabFolderViewLayout};
|
||||
use shared_entity::dto::workspace_dto::{FolderView, ViewLayout};
|
||||
use shared_entity::dto::workspace_dto::{FolderView, FolderViewMinimal, ViewLayout};
|
||||
|
||||
/// Return all folders belonging to a workspace, excluding private sections which the user does not have access to.
|
||||
pub fn collab_folder_to_folder_view(
|
||||
|
@ -108,7 +108,7 @@ fn to_folder_view(
|
|||
is_space: view_is_space(&view),
|
||||
is_private,
|
||||
is_published: published_view_ids.contains(view_id),
|
||||
layout: to_view_layout(&view.layout),
|
||||
layout: to_dto_view_layout(&view.layout),
|
||||
created_at: DateTime::from_timestamp(view.created_at, 0).unwrap_or_default(),
|
||||
last_edited_time: DateTime::from_timestamp(view.last_edited_time, 0).unwrap_or_default(),
|
||||
extra,
|
||||
|
@ -134,7 +134,7 @@ pub fn section_items_to_folder_view(
|
|||
is_published: published_view_ids.contains(&v.id),
|
||||
created_at: DateTime::from_timestamp(v.created_at, 0).unwrap_or_default(),
|
||||
last_edited_time: DateTime::from_timestamp(v.last_edited_time, 0).unwrap_or_default(),
|
||||
layout: to_view_layout(&v.layout),
|
||||
layout: to_dto_view_layout(&v.layout),
|
||||
extra: v.extra.as_ref().map(|e| parse_extra_field_as_json(e)),
|
||||
children: vec![],
|
||||
})
|
||||
|
@ -186,7 +186,7 @@ pub fn to_dto_view_icon_type(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_view_layout(collab_folder_view_layout: &CollabFolderViewLayout) -> ViewLayout {
|
||||
pub fn to_dto_view_layout(collab_folder_view_layout: &CollabFolderViewLayout) -> ViewLayout {
|
||||
match collab_folder_view_layout {
|
||||
CollabFolderViewLayout::Document => ViewLayout::Document,
|
||||
CollabFolderViewLayout::Grid => ViewLayout::Grid,
|
||||
|
@ -195,3 +195,12 @@ pub fn to_view_layout(collab_folder_view_layout: &CollabFolderViewLayout) -> Vie
|
|||
CollabFolderViewLayout::Chat => ViewLayout::Chat,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_dto_folder_view_miminal(collab_folder_view: &collab_folder::View) -> FolderViewMinimal {
|
||||
FolderViewMinimal {
|
||||
view_id: collab_folder_view.id.clone(),
|
||||
name: collab_folder_view.name.clone(),
|
||||
icon: collab_folder_view.icon.clone().map(to_dto_view_icon),
|
||||
layout: to_dto_view_layout(&collab_folder_view.layout),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ pub async fn get_collab_member_list(
|
|||
}
|
||||
|
||||
pub async fn get_user_favorite_folder_views(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
pg_pool: &PgPool,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
|
@ -193,7 +193,7 @@ pub async fn get_user_favorite_folder_views(
|
|||
}
|
||||
|
||||
pub async fn get_user_recent_folder_views(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
pg_pool: &PgPool,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
|
@ -227,7 +227,7 @@ pub async fn get_user_recent_folder_views(
|
|||
}
|
||||
|
||||
pub async fn get_user_trash_folder_views(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
) -> Result<Vec<FolderView>, AppError> {
|
||||
|
@ -246,7 +246,7 @@ pub async fn get_user_trash_folder_views(
|
|||
}
|
||||
|
||||
pub async fn get_user_workspace_structure(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
pg_pool: &PgPool,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
|
@ -275,7 +275,7 @@ pub async fn get_user_workspace_structure(
|
|||
}
|
||||
|
||||
pub async fn get_latest_collab_folder(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
collab_origin: GetCollabOrigin,
|
||||
workspace_id: &str,
|
||||
) -> Result<Folder, AppError> {
|
||||
|
@ -305,7 +305,7 @@ pub async fn get_latest_collab_folder(
|
|||
}
|
||||
|
||||
pub async fn get_latest_collab_encoded(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
collab_origin: GetCollabOrigin,
|
||||
workspace_id: &str,
|
||||
oid: &str,
|
||||
|
@ -327,7 +327,7 @@ pub async fn get_latest_collab_encoded(
|
|||
}
|
||||
|
||||
pub async fn get_published_view(
|
||||
collab_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
publish_namespace: String,
|
||||
pg_pool: &PgPool,
|
||||
) -> Result<PublishedView, AppError> {
|
||||
|
|
|
@ -4,7 +4,7 @@ use app_error::AppError;
|
|||
use collab_folder::Folder;
|
||||
use shared_entity::dto::workspace_dto::PublishedView;
|
||||
|
||||
use super::folder_view::{to_dto_view_icon, to_view_layout};
|
||||
use super::folder_view::{to_dto_view_icon, to_dto_view_layout};
|
||||
|
||||
/// Returns only folders that are published, or one of the nested subfolders is published.
|
||||
/// Exclude folders that are in the trash.
|
||||
|
@ -90,7 +90,7 @@ fn to_publish_view(
|
|||
.as_ref()
|
||||
.map(|icon| to_dto_view_icon(icon.clone())),
|
||||
is_published,
|
||||
layout: to_view_layout(&view.layout),
|
||||
layout: to_dto_view_layout(&view.layout),
|
||||
extra,
|
||||
children: pruned_view,
|
||||
})
|
||||
|
|
|
@ -23,7 +23,7 @@ use yrs::Update;
|
|||
|
||||
use crate::api::metrics::AppFlowyWebMetrics;
|
||||
use crate::biz::collab::folder_view::{
|
||||
parse_extra_field_as_json, to_dto_view_icon, to_view_layout,
|
||||
parse_extra_field_as_json, to_dto_view_icon, to_dto_view_layout,
|
||||
};
|
||||
use crate::biz::collab::{
|
||||
folder_view::view_is_space,
|
||||
|
@ -40,7 +40,7 @@ pub async fn get_page_view_collab(
|
|||
view_id: &str,
|
||||
) -> Result<PageCollab, AppResponseError> {
|
||||
let folder = get_latest_collab_folder(
|
||||
collab_access_control_storage.clone(),
|
||||
&collab_access_control_storage,
|
||||
GetCollabOrigin::User { uid },
|
||||
&workspace_id.to_string(),
|
||||
)
|
||||
|
@ -73,7 +73,7 @@ pub async fn get_page_view_collab(
|
|||
is_space: view_is_space(&view),
|
||||
is_private: false,
|
||||
is_published: publish_view_ids.contains(view_id),
|
||||
layout: to_view_layout(&view.layout),
|
||||
layout: to_dto_view_layout(&view.layout),
|
||||
created_at: DateTime::from_timestamp(view.created_at, 0).unwrap_or_default(),
|
||||
last_edited_time: DateTime::from_timestamp(view.last_edited_time, 0).unwrap_or_default(),
|
||||
extra: view.extra.as_ref().map(|e| parse_extra_field_as_json(e)),
|
||||
|
@ -81,13 +81,8 @@ pub async fn get_page_view_collab(
|
|||
};
|
||||
let page_collab_data = match view.layout {
|
||||
collab_folder::ViewLayout::Document => {
|
||||
get_page_collab_data_for_document(
|
||||
collab_access_control_storage.clone(),
|
||||
uid,
|
||||
workspace_id,
|
||||
view_id,
|
||||
)
|
||||
.await
|
||||
get_page_collab_data_for_document(&collab_access_control_storage, uid, workspace_id, view_id)
|
||||
.await
|
||||
},
|
||||
collab_folder::ViewLayout::Grid
|
||||
| collab_folder::ViewLayout::Board
|
||||
|
@ -126,7 +121,7 @@ async fn get_page_collab_data_for_database(
|
|||
) -> Result<PageCollabData, AppResponseError> {
|
||||
let ws_db_oid = select_workspace_database_oid(pg_pool, &workspace_id).await?;
|
||||
let ws_db = get_latest_collab_encoded(
|
||||
collab_access_control_storage.clone(),
|
||||
&collab_access_control_storage,
|
||||
GetCollabOrigin::User { uid },
|
||||
&workspace_id.to_string(),
|
||||
&ws_db_oid,
|
||||
|
@ -147,7 +142,7 @@ async fn get_page_collab_data_for_database(
|
|||
.database_id
|
||||
};
|
||||
let db = get_latest_collab_encoded(
|
||||
collab_access_control_storage.clone(),
|
||||
&collab_access_control_storage,
|
||||
GetCollabOrigin::User { uid },
|
||||
&workspace_id.to_string(),
|
||||
&db_oid,
|
||||
|
@ -225,13 +220,13 @@ async fn get_page_collab_data_for_database(
|
|||
}
|
||||
|
||||
async fn get_page_collab_data_for_document(
|
||||
collab_access_control_storage: Arc<CollabAccessControlStorage>,
|
||||
collab_access_control_storage: &CollabAccessControlStorage,
|
||||
uid: i64,
|
||||
workspace_id: Uuid,
|
||||
view_id: &str,
|
||||
) -> Result<PageCollabData, AppResponseError> {
|
||||
let collab = get_latest_collab_encoded(
|
||||
collab_access_control_storage.clone(),
|
||||
collab_access_control_storage,
|
||||
GetCollabOrigin::User { uid },
|
||||
&workspace_id.to_string(),
|
||||
view_id,
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
|
||||
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,
|
||||
},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use app_error::AppError;
|
||||
use async_trait::async_trait;
|
||||
use database_entity::dto::{PublishCollabItem, PublishInfo};
|
||||
use shared_entity::dto::publish_dto::PublishViewMetaData;
|
||||
use shared_entity::dto::{
|
||||
publish_dto::PublishViewMetaData,
|
||||
workspace_dto::{FolderViewMinimal, PublishInfoView},
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
use tracing::debug;
|
||||
use uuid::Uuid;
|
||||
|
@ -21,7 +32,10 @@ use database::{
|
|||
workspace::select_user_is_workspace_owner,
|
||||
};
|
||||
|
||||
use crate::api::metrics::PublishedCollabMetrics;
|
||||
use crate::{
|
||||
api::metrics::PublishedCollabMetrics,
|
||||
biz::collab::{folder_view::to_dto_folder_view_miminal, ops::get_latest_collab_folder},
|
||||
};
|
||||
|
||||
use super::ops::check_workspace_owner;
|
||||
|
||||
|
@ -86,6 +100,61 @@ pub async fn set_workspace_namespace(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_workspace_default_publish_view(
|
||||
pg_pool: &PgPool,
|
||||
user_uuid: &Uuid,
|
||||
workspace_id: &Uuid,
|
||||
new_view_id: &Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
check_workspace_owner(pg_pool, user_uuid, workspace_id).await?;
|
||||
update_workspace_default_publish_view(pg_pool, workspace_id, new_view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_workspace_default_publish_view_info(
|
||||
pg_pool: &PgPool,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<PublishInfo, AppError> {
|
||||
let view_id = select_default_published_view_id(pg_pool, workspace_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
AppError::RecordNotFound(format!(
|
||||
"Default published view not found for workspace_id: {}",
|
||||
workspace_id
|
||||
))
|
||||
})?;
|
||||
|
||||
let pub_info = select_published_collab_info(pg_pool, &view_id).await?;
|
||||
Ok(pub_info)
|
||||
}
|
||||
|
||||
pub async fn get_workspace_default_publish_view_info_meta(
|
||||
pg_pool: &PgPool,
|
||||
namespace: &str,
|
||||
) -> Result<(PublishInfo, serde_json::Value), AppError> {
|
||||
let view_id = select_default_published_view_id_for_namespace(pg_pool, namespace)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
AppError::RecordNotFound(format!(
|
||||
"Default published view not found for namespace: {}",
|
||||
namespace
|
||||
))
|
||||
})?;
|
||||
|
||||
let (pub_info, meta) = tokio::try_join!(
|
||||
select_published_collab_info(pg_pool, &view_id),
|
||||
select_published_metadata_for_view_id(pg_pool, &view_id)
|
||||
)?;
|
||||
let meta = meta.ok_or_else(|| {
|
||||
AppError::RecordNotFound(format!(
|
||||
"Published metadata not found for view_id: {}",
|
||||
view_id
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok((pub_info, meta.1))
|
||||
}
|
||||
|
||||
pub async fn get_workspace_publish_namespace(
|
||||
pg_pool: &PgPool,
|
||||
workspace_id: &Uuid,
|
||||
|
@ -93,20 +162,51 @@ pub async fn get_workspace_publish_namespace(
|
|||
select_workspace_publish_namespace(pg_pool, workspace_id).await
|
||||
}
|
||||
|
||||
pub async fn list_collab_publish_info(
|
||||
publish_collab_store: &dyn PublishedCollabStore,
|
||||
collab_storage: &CollabAccessControlStorage,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<Vec<PublishInfoView>, AppError> {
|
||||
let folder = get_latest_collab_folder(
|
||||
collab_storage,
|
||||
GetCollabOrigin::Server,
|
||||
&workspace_id.to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let publish_infos = publish_collab_store
|
||||
.list_collab_publish_info(workspace_id)
|
||||
.await?;
|
||||
|
||||
let mut publish_info_views: Vec<PublishInfoView> = Vec::with_capacity(publish_infos.len());
|
||||
for publish_info in publish_infos {
|
||||
let view_id = publish_info.view_id.to_string();
|
||||
match folder.get_view(&view_id) {
|
||||
Some(view) => {
|
||||
publish_info_views.push(PublishInfoView {
|
||||
view: to_dto_folder_view_miminal(&view),
|
||||
info: publish_info,
|
||||
});
|
||||
},
|
||||
None => {
|
||||
tracing::error!("View {} not found in folder but is published", view_id);
|
||||
publish_info_views.push(PublishInfoView {
|
||||
view: FolderViewMinimal {
|
||||
view_id,
|
||||
name: publish_info.publish_name.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
info: publish_info,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Ok(publish_info_views)
|
||||
}
|
||||
|
||||
async fn check_workspace_namespace(new_namespace: &str) -> Result<(), AppError> {
|
||||
// Check len
|
||||
if new_namespace.len() < 8 {
|
||||
return Err(AppError::InvalidRequest(
|
||||
"Namespace must be at least 8 characters long".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if new_namespace.len() > 64 {
|
||||
return Err(AppError::InvalidRequest(
|
||||
"Namespace must be at most 32 characters long".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Must be url safe
|
||||
// Only contain alphanumeric characters and hyphens
|
||||
for c in new_namespace.chars() {
|
||||
if !c.is_alphanumeric() && c != '-' {
|
||||
|
@ -115,9 +215,6 @@ async fn check_workspace_namespace(new_namespace: &str) -> Result<(), AppError>
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add more checks for reserved words
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -141,6 +238,11 @@ pub trait PublishedCollabStore: Sync + Send + 'static {
|
|||
publish_name: &str,
|
||||
) -> Result<serde_json::Value, AppError>;
|
||||
|
||||
async fn list_collab_publish_info(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<Vec<PublishInfo>, AppError>;
|
||||
|
||||
async fn get_collab_publish_info(&self, view_id: &Uuid) -> Result<PublishInfo, AppError>;
|
||||
|
||||
async fn get_collab_blob_by_publish_namespace(
|
||||
|
@ -224,6 +326,13 @@ impl PublishedCollabStore for PublishedCollabPostgresStore {
|
|||
result
|
||||
}
|
||||
|
||||
async fn list_collab_publish_info(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<Vec<PublishInfo>, AppError> {
|
||||
select_all_published_collab_info(&self.pg_pool, workspace_id).await
|
||||
}
|
||||
|
||||
async fn get_collab_publish_info(&self, view_id: &Uuid) -> Result<PublishInfo, AppError> {
|
||||
select_published_collab_info(&self.pg_pool, view_id).await
|
||||
}
|
||||
|
@ -371,6 +480,13 @@ impl PublishedCollabStore for PublishedCollabS3StoreWithPostgresFallback {
|
|||
select_published_collab_info(&self.pg_pool, view_id).await
|
||||
}
|
||||
|
||||
async fn list_collab_publish_info(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<Vec<PublishInfo>, AppError> {
|
||||
select_all_published_collab_info(&self.pg_pool, workspace_id).await
|
||||
}
|
||||
|
||||
async fn get_collab_blob_by_publish_namespace(
|
||||
&self,
|
||||
publish_namespace: &str,
|
||||
|
|
|
@ -198,7 +198,7 @@ impl PublishCollabDuplicator {
|
|||
let ws_db_oid = select_workspace_database_oid(&pg_pool, &dest_workspace_id.parse()?).await?;
|
||||
let ws_db_collab = {
|
||||
let ws_database_ec = get_latest_collab_encoded(
|
||||
collab_storage.clone(),
|
||||
&collab_storage,
|
||||
GetCollabOrigin::User {
|
||||
uid: duplicator_uid,
|
||||
},
|
||||
|
@ -256,7 +256,7 @@ impl PublishCollabDuplicator {
|
|||
}
|
||||
|
||||
let collab_folder_encoded = get_latest_collab_encoded(
|
||||
collab_storage.clone(),
|
||||
&collab_storage,
|
||||
GetCollabOrigin::User {
|
||||
uid: duplicator_uid,
|
||||
},
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
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};
|
||||
use client_api::entity::{
|
||||
AFRole, GlobalComment, PublishCollabItem, PublishCollabMetadata, PublishInfoMeta,
|
||||
};
|
||||
use client_api_test::TestClient;
|
||||
use client_api_test::{generate_unique_registered_user_client, localhost_client};
|
||||
use collab::util::MapExt;
|
||||
|
@ -47,7 +49,12 @@ async fn test_set_publish_namespace_set() {
|
|||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(format!("{:?}", err.code), "PublishNamespaceAlreadyTaken");
|
||||
assert_eq!(
|
||||
err.code,
|
||||
ErrorCode::PublishNamespaceAlreadyTaken,
|
||||
"{:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
{
|
||||
// can replace the namespace
|
||||
|
@ -62,16 +69,6 @@ async fn test_set_publish_namespace_set() {
|
|||
.unwrap();
|
||||
assert_eq!(got_namespace, namespace);
|
||||
}
|
||||
{
|
||||
// cannot set namespace too short
|
||||
let err = c
|
||||
.set_workspace_publish_namespace(&workspace_id.to_string(), "a") // too short
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(format!("{:?}", err.code), "InvalidRequest");
|
||||
}
|
||||
|
||||
{
|
||||
// cannot set namespace with invalid chars
|
||||
let err = c
|
||||
|
@ -79,7 +76,7 @@ async fn test_set_publish_namespace_set() {
|
|||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(format!("{:?}", err.code), "InvalidRequest");
|
||||
assert_eq!(err.code, ErrorCode::InvalidRequest, "{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,6 +122,23 @@ async fn test_publish_doc() {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
// Check that the published collabs are listed
|
||||
let published_view_infos = c.list_published_views(&workspace_id).await.unwrap();
|
||||
assert_eq!(published_view_infos.len(), 2);
|
||||
let view_info_1 = published_view_infos
|
||||
.iter()
|
||||
.find(|view_info| view_info.info.publish_name == publish_name_1)
|
||||
.unwrap();
|
||||
assert_eq!(view_info_1.info.view_id, view_id_1);
|
||||
|
||||
let view_info_2 = published_view_infos
|
||||
.iter()
|
||||
.find(|view_info| view_info.info.publish_name == publish_name_2)
|
||||
.unwrap();
|
||||
assert_eq!(view_info_2.info.view_id, view_id_2);
|
||||
}
|
||||
|
||||
{
|
||||
// Non login user should be able to view the published collab
|
||||
let guest_client = localhost_client();
|
||||
|
@ -208,7 +222,36 @@ async fn test_publish_doc() {
|
|||
assert_eq!(blob, "yrs_encoded_data_4");
|
||||
}
|
||||
|
||||
c.unpublish_collabs(&workspace_id, &[view_id_1])
|
||||
{
|
||||
// Try to get default publish view info but not set
|
||||
let err = c
|
||||
.get_default_publish_view_info(&workspace_id)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound, "{:?}", err);
|
||||
|
||||
// Set publish view as default workspace view
|
||||
c.set_default_publish_view(&workspace_id, view_id_1.to_owned())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Get default publish view
|
||||
let publish_info = c
|
||||
.get_default_publish_view_info(&workspace_id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(publish_info.view_id, view_id_1);
|
||||
|
||||
// Public can use namespace to get default publish view info and view metadata
|
||||
let default_info_meta: PublishInfoMeta<MyCustomMetadata> = localhost_client()
|
||||
.get_default_published_collab(&my_namespace)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(default_info_meta.info.view_id, view_id_1);
|
||||
assert_eq!(default_info_meta.meta.title, "my_title_1");
|
||||
}
|
||||
|
||||
c.unpublish_collabs(&workspace_id, &[view_id_1, view_id_2])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -220,7 +263,7 @@ async fn test_publish_doc() {
|
|||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(format!("{:?}", err.code), "RecordNotFound");
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound, "{:?}", err);
|
||||
|
||||
let guest_client = localhost_client();
|
||||
let err = guest_client
|
||||
|
@ -228,7 +271,21 @@ async fn test_publish_doc() {
|
|||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(format!("{:?}", err.code), "RecordNotFound");
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound, "{:?}", err);
|
||||
|
||||
// default publish view should not be accessible
|
||||
let err = c
|
||||
.get_default_publish_view_info(&workspace_id)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound, "{:?}", err);
|
||||
}
|
||||
|
||||
{
|
||||
// check that the published collabs are removed
|
||||
// listing published collab should return empty
|
||||
let published_infos = c.list_published_views(&workspace_id).await.unwrap();
|
||||
assert_eq!(published_infos.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue