feat: add publish info to publish outline items (#1156)

This commit is contained in:
Khor Shu Heng 2025-01-12 13:08:38 +08:00 committed by GitHub
parent 505b4ca8fc
commit a5d94a09d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 121 additions and 14 deletions

View File

@ -46,6 +46,7 @@ jobs:
- name: Build Docker Images
run: |
export DOCKER_DEFAULT_PLATFORM=linux/amd64
cp deploy.env .env
docker compose build appflowy_cloud appflowy_worker admin_frontend
- name: Push docker images to docker hub

View File

@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n apc.view_id,\n apc.publish_name,\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 WHERE workspace_id = $1\n AND unpublished_at IS NULL\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "view_id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "publish_name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "publisher_email",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "publish_timestamp",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "4787139d2189fc33ac25ac07bb9f585f578bde4cd3f75a7da95aa849a25bca9d"
}

View File

@ -686,3 +686,10 @@ impl From<AFQuickNoteRow> for QuickNote {
}
}
}
pub struct AFPublishViewWithPublishInfo {
pub view_id: Uuid,
pub publish_name: String,
pub publisher_email: String,
pub publish_timestamp: DateTime<Utc>,
}

View File

@ -5,6 +5,8 @@ use database_entity::dto::{
use sqlx::{Executor, PgPool, Postgres};
use uuid::Uuid;
use crate::pg_row::AFPublishViewWithPublishInfo;
pub async fn select_user_is_collab_publisher_for_all_views(
pg_pool: &PgPool,
user_uuid: &Uuid,
@ -678,3 +680,31 @@ pub async fn select_published_view_ids_for_workspace<'a, E: Executor<'a, Databas
Ok(res)
}
pub async fn select_published_view_ids_with_publish_info_for_workspace<
'a,
E: Executor<'a, Database = Postgres>,
>(
executor: E,
workspace_id: Uuid,
) -> Result<Vec<AFPublishViewWithPublishInfo>, AppError> {
let res = sqlx::query_as!(
AFPublishViewWithPublishInfo,
r#"
SELECT
apc.view_id,
apc.publish_name,
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
WHERE workspace_id = $1
AND unpublished_at IS NULL
"#,
workspace_id,
)
.fetch_all(executor)
.await?;
Ok(res)
}

View File

@ -389,11 +389,20 @@ pub struct PublishedView {
pub icon: Option<ViewIcon>,
pub layout: ViewLayout,
pub is_published: bool,
#[serde(flatten)]
pub info: Option<PublishedViewInfo>,
/// contains fields like `is_space`, and font information
pub extra: Option<serde_json::Value>,
pub children: Vec<PublishedView>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PublishedViewInfo {
pub publisher_email: String,
pub publish_name: String,
pub publish_timestamp: DateTime<Utc>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct AFDatabase {
pub id: String,

View File

@ -34,6 +34,7 @@ use database::collab::select_last_updated_database_row_ids;
use database::collab::select_workspace_database_oid;
use database::collab::{CollabStorage, GetCollabOrigin};
use database::publish::select_published_view_ids_for_workspace;
use database::publish::select_published_view_ids_with_publish_info_for_workspace;
use database::publish::select_workspace_id_for_publish_namespace;
use database_entity::dto::QueryCollab;
use database_entity::dto::QueryCollabResult;
@ -46,6 +47,7 @@ use shared_entity::dto::workspace_dto::AFInsertDatabaseField;
use shared_entity::dto::workspace_dto::DatabaseRowUpdatedItem;
use shared_entity::dto::workspace_dto::FavoriteFolderView;
use shared_entity::dto::workspace_dto::FolderViewMinimal;
use shared_entity::dto::workspace_dto::PublishedViewInfo;
use shared_entity::dto::workspace_dto::RecentFolderView;
use shared_entity::dto::workspace_dto::TrashFolderView;
use sqlx::PgPool;
@ -457,13 +459,28 @@ pub async fn get_published_view(
&workspace_id.to_string(),
)
.await?;
let publish_view_ids = select_published_view_ids_for_workspace(pg_pool, workspace_id).await?;
let publish_view_ids: HashSet<String> = publish_view_ids
.into_iter()
.map(|id| id.to_string())
.collect();
let published_view: PublishedView =
collab_folder_to_published_outline(&workspace_id.to_string(), &folder, &publish_view_ids)?;
let publish_view_ids_with_publish_info =
select_published_view_ids_with_publish_info_for_workspace(pg_pool, workspace_id).await?;
let publish_view_id_to_info_map: HashMap<String, PublishedViewInfo> =
publish_view_ids_with_publish_info
.into_iter()
.map(|pv| {
(
pv.view_id.to_string(),
PublishedViewInfo {
publisher_email: pv.publisher_email.clone(),
publish_name: pv.publish_name.clone(),
publish_timestamp: pv.publish_timestamp,
},
)
})
.collect();
let published_view: PublishedView = collab_folder_to_published_outline(
&workspace_id.to_string(),
&folder,
&publish_view_id_to_info_map,
)?;
Ok(published_view)
}

View File

@ -1,8 +1,8 @@
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use app_error::AppError;
use collab_folder::Folder;
use shared_entity::dto::workspace_dto::PublishedView;
use shared_entity::dto::workspace_dto::{PublishedView, PublishedViewInfo};
use super::folder_view::{to_dto_view_icon, to_dto_view_layout};
@ -11,7 +11,7 @@ use super::folder_view::{to_dto_view_icon, to_dto_view_layout};
pub fn collab_folder_to_published_outline(
root_view_id: &str,
folder: &Folder,
publish_view_ids: &HashSet<String>,
publish_view_id_to_info_map: &HashMap<String, PublishedViewInfo>,
) -> Result<PublishedView, AppError> {
let mut unviewable = HashSet::new();
for trash_view in folder.get_all_trash_sections() {
@ -24,7 +24,7 @@ pub fn collab_folder_to_published_outline(
root_view_id,
folder,
&unviewable,
publish_view_ids,
publish_view_id_to_info_map,
0,
max_depth,
)
@ -39,7 +39,7 @@ fn to_publish_view(
view_id: &str,
folder: &Folder,
unviewable: &HashSet<String>,
publish_view_ids: &HashSet<String>,
publish_view_id_to_info_map: &HashMap<String, PublishedViewInfo>,
depth: u32,
max_depth: u32,
) -> Option<PublishedView> {
@ -65,6 +65,8 @@ fn to_publish_view(
serde_json::Value::Null
})
});
// If pruned_view is not empty, then one or more of the children is published.
// Hence, this view should be included in the published outline, even if it is not published itself.
let pruned_view: Vec<PublishedView> = view
.children
.iter()
@ -74,13 +76,13 @@ fn to_publish_view(
&child_view_id.id,
folder,
unviewable,
publish_view_ids,
publish_view_id_to_info_map,
depth + 1,
max_depth,
)
})
.collect();
let is_published = publish_view_ids.contains(view_id);
let is_published = publish_view_id_to_info_map.contains_key(view_id);
if parent_view_id.is_empty() || is_published || !pruned_view.is_empty() {
Some(PublishedView {
view_id: view.id.clone(),
@ -93,6 +95,7 @@ fn to_publish_view(
layout: to_dto_view_layout(&view.layout),
extra,
children: pruned_view,
info: publish_view_id_to_info_map.get(view_id).cloned(),
})
} else {
None