mirror of
https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
synced 2025-04-19 03:24:42 -04:00
feat: add can be deleted field to GlobalComment
This commit is contained in:
parent
928781d01e
commit
ff7de66dfa
9 changed files with 135 additions and 20 deletions
22
.sqlx/query-e9e22adc5a6f6daf354dc122cadab41b7cc13e0d956b3204d22f26a47ad594e3.json
generated
Normal file
22
.sqlx/query-e9e22adc5a6f6daf354dc122cadab41b7cc13e0d956b3204d22f26a47ad594e3.json
generated
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n af.uuid\n FROM af_published_collab apc\n JOIN af_user af ON af.uid = apc.published_by\n WHERE view_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "uuid",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e9e22adc5a6f6daf354dc122cadab41b7cc13e0d956b3204d22f26a47ad594e3"
|
||||
}
|
|
@ -58,6 +58,33 @@ impl FromRequest for UserUuid {
|
|||
}
|
||||
}
|
||||
|
||||
// For cases where the handler itself will handle the request differently
|
||||
// based on whether the user is authenticated or not
|
||||
pub struct OptionalUserUuid(Option<UserUuid>);
|
||||
|
||||
impl FromRequest for OptionalUserUuid {
|
||||
type Error = actix_web::Error;
|
||||
|
||||
type Future = std::future::Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||
let auth = get_auth_from_request(req);
|
||||
match auth {
|
||||
Ok(auth) => match UserUuid::from_auth(auth) {
|
||||
Ok(uuid) => std::future::ready(Ok(OptionalUserUuid(Some(uuid)))),
|
||||
Err(_) => std::future::ready(Ok(OptionalUserUuid(None))),
|
||||
},
|
||||
Err(_) => std::future::ready(Ok(OptionalUserUuid(None))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionalUserUuid {
|
||||
pub fn as_uuid(&self) -> Option<uuid::Uuid> {
|
||||
self.0.as_deref().map(|uuid| uuid.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Authorization {
|
||||
pub token: String,
|
||||
|
|
|
@ -843,6 +843,16 @@ impl Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn http_client_without_auth(
|
||||
&self,
|
||||
method: Method,
|
||||
url: &str,
|
||||
) -> Result<RequestBuilder, AppResponseError> {
|
||||
trace!("start request: {}, method: {}", url, method,);
|
||||
Ok(self.cloud_client.request(method, url))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn http_client_with_auth(
|
||||
&self,
|
||||
|
|
|
@ -153,6 +153,29 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
// Optional login
|
||||
impl Client {
|
||||
pub async fn get_published_view_comments(
|
||||
&self,
|
||||
view_id: &uuid::Uuid,
|
||||
) -> Result<GlobalComments, AppResponseError> {
|
||||
let url = format!(
|
||||
"{}/api/workspace/published-info/{}/comment",
|
||||
self.base_url, view_id
|
||||
);
|
||||
let client = if let Ok(client) = self.http_client_with_auth(Method::GET, &url).await {
|
||||
client
|
||||
} else {
|
||||
self.http_client_without_auth(Method::GET, &url).await?
|
||||
};
|
||||
|
||||
let resp = client.send().await?;
|
||||
AppResponse::<GlobalComments>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
}
|
||||
|
||||
// Guest API (no login required)
|
||||
impl Client {
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
|
@ -236,20 +259,6 @@ impl Client {
|
|||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub async fn get_published_view_comments(
|
||||
&self,
|
||||
view_id: &uuid::Uuid,
|
||||
) -> Result<GlobalComments, AppResponseError> {
|
||||
let url = format!(
|
||||
"{}/api/workspace/published-info/{}/comment",
|
||||
self.base_url, view_id
|
||||
);
|
||||
let resp = self.cloud_client.get(&url).send().await?;
|
||||
AppResponse::<GlobalComments>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn get_published_view_reactions(
|
||||
&self,
|
||||
view_id: &uuid::Uuid,
|
||||
|
|
|
@ -865,6 +865,7 @@ pub struct GlobalComment {
|
|||
pub reply_comment_id: Option<Uuid>,
|
||||
pub comment_id: Uuid,
|
||||
pub is_deleted: bool,
|
||||
pub can_be_deleted: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
@ -1119,12 +1119,34 @@ pub async fn select_published_collab_info<'a, E: Executor<'a, Database = Postgre
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_comments_for_published_view_orderd_by_recency<
|
||||
pub async fn select_owner_of_published_collab<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
view_id: &Uuid,
|
||||
) -> Result<Uuid, AppError> {
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
af.uuid
|
||||
FROM af_published_collab apc
|
||||
JOIN af_user af ON af.uid = apc.published_by
|
||||
WHERE view_id = $1
|
||||
"#,
|
||||
view_id,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
Ok(res.uuid)
|
||||
}
|
||||
|
||||
pub async fn select_comments_for_published_view_ordered_by_recency<
|
||||
'a,
|
||||
E: Executor<'a, Database = Postgres>,
|
||||
>(
|
||||
executor: E,
|
||||
view_id: &Uuid,
|
||||
user_uuid: &Option<Uuid>,
|
||||
page_owner_uuid: &Uuid,
|
||||
) -> Result<Vec<GlobalComment>, AppError> {
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
|
@ -1158,6 +1180,8 @@ pub async fn select_comments_for_published_view_orderd_by_recency<
|
|||
.unwrap_or("".to_string()),
|
||||
avatar_url: None,
|
||||
});
|
||||
let is_page_owner = user_uuid.as_ref() == Some(page_owner_uuid);
|
||||
let is_comment_creator = user_uuid.as_ref() == comment_creator.as_ref().map(|u| &u.uuid);
|
||||
GlobalComment {
|
||||
user: comment_creator,
|
||||
comment_id: row.comment_id,
|
||||
|
@ -1166,6 +1190,7 @@ pub async fn select_comments_for_published_view_orderd_by_recency<
|
|||
content: row.content.clone(),
|
||||
reply_comment_id: row.reply_comment_id,
|
||||
is_deleted: row.is_deleted,
|
||||
can_be_deleted: !row.is_deleted && (is_page_owner || is_comment_creator),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -25,7 +25,7 @@ use access_control::collab::CollabAccessControl;
|
|||
use app_error::AppError;
|
||||
use appflowy_collaborate::actix_ws::entities::ClientStreamMessage;
|
||||
use appflowy_collaborate::indexer::IndexerProvider;
|
||||
use authentication::jwt::UserUuid;
|
||||
use authentication::jwt::{OptionalUserUuid, UserUuid};
|
||||
use collab_rt_entity::realtime_proto::HttpRealtimeMessage;
|
||||
use collab_rt_entity::RealtimeMessage;
|
||||
use collab_rt_protocol::validate_encode_collab;
|
||||
|
@ -1097,10 +1097,12 @@ async fn get_published_collab_info_handler(
|
|||
|
||||
async fn get_published_collab_comment_handler(
|
||||
view_id: web::Path<Uuid>,
|
||||
optional_user_uuid: OptionalUserUuid,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<GlobalComments>> {
|
||||
let view_id = view_id.into_inner();
|
||||
let comments = get_comments_on_published_view(&state.pg_pool, &view_id).await?;
|
||||
let comments =
|
||||
get_comments_on_published_view(&state.pg_pool, &view_id, &optional_user_uuid).await?;
|
||||
let resp = GlobalComments { comments };
|
||||
Ok(Json(AppResponse::Ok().with_data(resp)))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use authentication::jwt::OptionalUserUuid;
|
||||
use database_entity::dto::{AFWorkspaceSettingsChange, PublishCollabItem};
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -165,8 +166,16 @@ pub async fn get_published_collab_info(
|
|||
pub async fn get_comments_on_published_view(
|
||||
pg_pool: &PgPool,
|
||||
view_id: &Uuid,
|
||||
optional_user_uuid: &OptionalUserUuid,
|
||||
) -> Result<Vec<GlobalComment>, AppError> {
|
||||
let comments = select_comments_for_published_view_orderd_by_recency(pg_pool, view_id).await?;
|
||||
let page_owner_uuid = select_owner_of_published_collab(pg_pool, view_id).await?;
|
||||
let comments = select_comments_for_published_view_ordered_by_recency(
|
||||
pg_pool,
|
||||
view_id,
|
||||
&optional_user_uuid.as_uuid(),
|
||||
&page_owner_uuid,
|
||||
)
|
||||
.await?;
|
||||
Ok(comments)
|
||||
}
|
||||
|
||||
|
|
|
@ -267,26 +267,35 @@ async fn test_publish_comments() {
|
|||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().code, ErrorCode::NotLoggedIn);
|
||||
|
||||
// Test if only all users, authenticated or not, can view all the comments
|
||||
// Test if only all users, authenticated or not, can view all the comments,
|
||||
// and whether the `can_be_deleted` field is correctly set
|
||||
let published_view_comments: Vec<GlobalComment> = page_owner_client
|
||||
.get_published_view_comments(&view_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.comments;
|
||||
assert_eq!(published_view_comments.len(), 2);
|
||||
assert!(published_view_comments.iter().all(|c| c.can_be_deleted));
|
||||
let published_view_comments: Vec<GlobalComment> = first_user_client
|
||||
.get_published_view_comments(&view_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.comments;
|
||||
assert_eq!(published_view_comments.len(), 2);
|
||||
assert_eq!(
|
||||
published_view_comments
|
||||
.iter()
|
||||
.map(|c| c.can_be_deleted)
|
||||
.collect_vec(),
|
||||
vec![true, false]
|
||||
);
|
||||
let published_view_comments: Vec<GlobalComment> = guest_client
|
||||
.get_published_view_comments(&view_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.comments;
|
||||
assert_eq!(published_view_comments.len(), 2);
|
||||
assert!(published_view_comments.iter().all(|c| !c.is_deleted));
|
||||
assert!(published_view_comments.iter().all(|c| !c.can_be_deleted));
|
||||
|
||||
// Test if the comments are correctly sorted
|
||||
let comment_creators = published_view_comments
|
||||
|
@ -401,6 +410,7 @@ async fn test_publish_comments() {
|
|||
.comments;
|
||||
assert_eq!(published_view_comments.len(), 3);
|
||||
assert!(published_view_comments.iter().all(|c| c.is_deleted));
|
||||
assert!(published_view_comments.iter().all(|c| !c.can_be_deleted));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
Loading…
Add table
Reference in a new issue