From a5d94a09d63e869c6150a98fa1cb1cb3b6f03d73 Mon Sep 17 00:00:00 2001 From: Khor Shu Heng <32997938+khorshuheng@users.noreply.github.com> Date: Sun, 12 Jan 2025 13:08:38 +0800 Subject: [PATCH] feat: add publish info to publish outline items (#1156) --- .github/workflows/integration_test.yml | 1 + ...f585f578bde4cd3f75a7da95aa849a25bca9d.json | 40 +++++++++++++++++++ libs/database/src/pg_row.rs | 7 ++++ libs/database/src/publish.rs | 30 ++++++++++++++ libs/shared-entity/src/dto/workspace_dto.rs | 9 +++++ src/biz/collab/ops.rs | 31 ++++++++++---- src/biz/collab/publish_outline.rs | 17 ++++---- 7 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 .sqlx/query-4787139d2189fc33ac25ac07bb9f585f578bde4cd3f75a7da95aa849a25bca9d.json diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 9bf12bff..834044d7 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -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 diff --git a/.sqlx/query-4787139d2189fc33ac25ac07bb9f585f578bde4cd3f75a7da95aa849a25bca9d.json b/.sqlx/query-4787139d2189fc33ac25ac07bb9f585f578bde4cd3f75a7da95aa849a25bca9d.json new file mode 100644 index 00000000..63da7008 --- /dev/null +++ b/.sqlx/query-4787139d2189fc33ac25ac07bb9f585f578bde4cd3f75a7da95aa849a25bca9d.json @@ -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" +} diff --git a/libs/database/src/pg_row.rs b/libs/database/src/pg_row.rs index fece72b6..14798037 100644 --- a/libs/database/src/pg_row.rs +++ b/libs/database/src/pg_row.rs @@ -686,3 +686,10 @@ impl From for QuickNote { } } } + +pub struct AFPublishViewWithPublishInfo { + pub view_id: Uuid, + pub publish_name: String, + pub publisher_email: String, + pub publish_timestamp: DateTime, +} diff --git a/libs/database/src/publish.rs b/libs/database/src/publish.rs index e9f23a74..0fedb0aa 100644 --- a/libs/database/src/publish.rs +++ b/libs/database/src/publish.rs @@ -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, 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) +} diff --git a/libs/shared-entity/src/dto/workspace_dto.rs b/libs/shared-entity/src/dto/workspace_dto.rs index 174da343..ad75eb17 100644 --- a/libs/shared-entity/src/dto/workspace_dto.rs +++ b/libs/shared-entity/src/dto/workspace_dto.rs @@ -389,11 +389,20 @@ pub struct PublishedView { pub icon: Option, pub layout: ViewLayout, pub is_published: bool, + #[serde(flatten)] + pub info: Option, /// contains fields like `is_space`, and font information pub extra: Option, pub children: Vec, } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct PublishedViewInfo { + pub publisher_email: String, + pub publish_name: String, + pub publish_timestamp: DateTime, +} + #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct AFDatabase { pub id: String, diff --git a/src/biz/collab/ops.rs b/src/biz/collab/ops.rs index f44e34b4..ce45bc1b 100644 --- a/src/biz/collab/ops.rs +++ b/src/biz/collab/ops.rs @@ -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 = 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 = + 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) } diff --git a/src/biz/collab/publish_outline.rs b/src/biz/collab/publish_outline.rs index b18506f4..8b79d92c 100644 --- a/src/biz/collab/publish_outline.rs +++ b/src/biz/collab/publish_outline.rs @@ -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, + publish_view_id_to_info_map: &HashMap, ) -> Result { 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, - publish_view_ids: &HashSet, + publish_view_id_to_info_map: &HashMap, depth: u32, max_depth: u32, ) -> Option { @@ -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 = 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