AppFlowy-Cloud/libs/database/src/publish.rs

545 lines
13 KiB
Rust

use app_error::AppError;
use database_entity::dto::{
PatchPublishedCollab, PublishCollabItem, PublishCollabKey, PublishInfo,
};
use sqlx::{Executor, PgPool, Postgres};
use uuid::Uuid;
pub async fn select_user_is_collab_publisher_for_all_views(
pg_pool: &PgPool,
user_uuid: &Uuid,
workspace_uuid: &Uuid,
view_ids: &[Uuid],
) -> Result<bool, AppError> {
let count = sqlx::query_scalar!(
r#"
SELECT COUNT(*)
FROM af_published_collab
WHERE workspace_id = $1
AND view_id = ANY($2)
AND published_by = (SELECT uid FROM af_user WHERE uuid = $3)
"#,
workspace_uuid,
view_ids,
user_uuid,
)
.fetch_one(pg_pool)
.await?;
match count {
Some(c) => Ok(c == view_ids.len() as i64),
None => Ok(false),
}
}
#[inline]
pub async fn select_workspace_publish_namespace_exists<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: &Uuid,
namespace: &str,
) -> Result<bool, AppError> {
let res = sqlx::query_scalar!(
r#"
SELECT EXISTS(
SELECT 1
FROM af_workspace
WHERE workspace_id = $1
AND publish_namespace = $2
)
"#,
workspace_id,
namespace,
)
.fetch_one(executor)
.await?;
Ok(res.unwrap_or(false))
}
#[inline]
pub async fn update_workspace_publish_namespace<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: &Uuid,
new_namespace: &str,
) -> Result<(), AppError> {
let res = sqlx::query!(
r#"
UPDATE af_workspace
SET publish_namespace = $1
WHERE workspace_id = $2
"#,
new_namespace,
workspace_id,
)
.execute(executor)
.await?;
if res.rows_affected() != 1 {
tracing::error!(
"Failed to update workspace publish namespace, workspace_id: {}, new_namespace: {}, rows_affected: {}",
workspace_id, new_namespace, res.rows_affected()
);
}
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 update_workspace_default_publish_view_set_null<
'a,
E: Executor<'a, Database = Postgres>,
>(
executor: E,
workspace_id: &Uuid,
) -> Result<(), AppError> {
let res = sqlx::query!(
r#"
UPDATE af_workspace
SET default_published_view_id = NULL
WHERE workspace_id = $1
"#,
workspace_id,
)
.execute(executor)
.await?;
if res.rows_affected() != 1 {
tracing::error!(
"Failed to unset workspace default publish view, workspace_id: {}, rows_affected: {}",
workspace_id,
res.rows_affected()
);
}
Ok(())
}
#[inline]
pub async fn select_workspace_publish_namespace<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: &Uuid,
) -> Result<String, AppError> {
let res = sqlx::query_scalar!(
r#"
SELECT publish_namespace
FROM af_workspace
WHERE workspace_id = $1
"#,
workspace_id,
)
.fetch_one(executor)
.await?;
Ok(res)
}
#[inline]
pub async fn insert_or_replace_publish_collabs<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: &Uuid,
publisher_uuid: &Uuid,
publish_items: Vec<PublishCollabItem<serde_json::Value, Vec<u8>>>,
) -> Result<(), AppError> {
let item_count = publish_items.len();
let mut view_ids: Vec<Uuid> = Vec::with_capacity(item_count);
let mut publish_names: Vec<String> = Vec::with_capacity(item_count);
let mut metadatas: Vec<serde_json::Value> = Vec::with_capacity(item_count);
let mut blobs: Vec<Vec<u8>> = Vec::with_capacity(item_count);
publish_items.into_iter().for_each(|item| {
view_ids.push(item.meta.view_id);
publish_names.push(item.meta.publish_name);
metadatas.push(item.meta.metadata);
blobs.push(item.data);
});
let res = sqlx::query!(
r#"
INSERT INTO af_published_collab (workspace_id, view_id, publish_name, published_by, metadata, blob)
SELECT * FROM UNNEST(
(SELECT array_agg((SELECT $1::uuid)) FROM generate_series(1, $7))::uuid[],
$2::uuid[],
$3::text[],
(SELECT array_agg((SELECT uid FROM af_user WHERE uuid = $4)) FROM generate_series(1, $7))::bigint[],
$5::jsonb[],
$6::bytea[]
)
ON CONFLICT (workspace_id, view_id) DO UPDATE
SET metadata = EXCLUDED.metadata,
blob = EXCLUDED.blob,
published_by = EXCLUDED.published_by,
publish_name = EXCLUDED.publish_name
"#,
workspace_id,
&view_ids,
&publish_names,
publisher_uuid,
&metadatas,
&blobs,
item_count as i32,
)
.execute(executor)
.await?;
if res.rows_affected() != item_count as u64 {
tracing::warn!(
"Failed to insert or replace publish collab meta batch, workspace_id: {}, publisher_uuid: {}, rows_affected: {}, item_count: {}",
workspace_id, publisher_uuid, res.rows_affected(), item_count
);
}
Ok(())
}
#[inline]
pub async fn select_publish_collab_meta<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
publish_namespace: &str,
publish_name: &str,
) -> Result<serde_json::Value, AppError> {
let res = sqlx::query!(
r#"
SELECT metadata
FROM af_published_collab
WHERE workspace_id = (SELECT workspace_id FROM af_workspace WHERE publish_namespace = $1)
AND publish_name = $2
"#,
publish_namespace,
publish_name,
)
.fetch_one(executor)
.await?;
let metadata: serde_json::Value = res.metadata;
Ok(metadata)
}
#[inline]
pub async fn delete_published_collabs<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: &Uuid,
view_ids: &[Uuid],
) -> Result<(), AppError> {
let res = sqlx::query!(
r#"
DELETE FROM af_published_collab
WHERE workspace_id = $1
AND view_id = ANY($2)
"#,
workspace_id,
view_ids,
)
.execute(executor)
.await?;
if res.rows_affected() != view_ids.len() as u64 {
tracing::error!(
"Failed to delete published collabs, workspace_id: {}, view_ids: {:?}, rows_affected: {}",
workspace_id,
view_ids,
res.rows_affected()
);
}
Ok(())
}
#[inline]
pub async fn update_published_collabs(
txn: &mut sqlx::Transaction<'_, Postgres>,
workspace_id: &Uuid,
patches: &[PatchPublishedCollab],
) -> Result<(), AppError> {
for patch in patches {
let new_publish_name = match &patch.publish_name {
Some(new_publish_name) => new_publish_name,
None => continue,
};
let res = sqlx::query!(
r#"
UPDATE af_published_collab
SET publish_name = $1
WHERE workspace_id = $2
AND view_id = $3
"#,
patch.publish_name,
workspace_id,
patch.view_id,
)
.execute(txn.as_mut())
.await?;
if res.rows_affected() != 1 {
tracing::error!(
"Failed to update published collab publish name, workspace_id: {}, view_id: {}, new_publish_name: {}, rows_affected: {}",
workspace_id,
patch.view_id,
new_publish_name,
res.rows_affected()
);
}
}
Ok(())
}
#[inline]
pub async fn select_published_metadata_for_view_id(
pg_pool: &PgPool,
view_id: &Uuid,
) -> Result<Option<(Uuid, serde_json::Value)>, AppError> {
let res = sqlx::query!(
r#"
SELECT workspace_id, metadata
FROM af_published_collab
WHERE view_id = $1
"#,
view_id,
)
.fetch_optional(pg_pool)
.await?;
Ok(res.map(|res| (res.workspace_id, res.metadata)))
}
#[inline]
pub async fn select_published_data_for_view_id(
pg_pool: &PgPool,
view_id: &Uuid,
) -> Result<Option<(serde_json::Value, Vec<u8>)>, AppError> {
let res = sqlx::query!(
r#"
SELECT metadata, blob
FROM af_published_collab
WHERE view_id = $1
"#,
view_id,
)
.fetch_optional(pg_pool)
.await?;
Ok(res.map(|res| (res.metadata, res.blob)))
}
#[inline]
pub async fn select_published_collab_workspace_view_id<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
publish_namespace: &str,
publish_name: &str,
) -> Result<PublishCollabKey, AppError> {
let key = sqlx::query_as!(
PublishCollabKey,
r#"
SELECT workspace_id, view_id
FROM af_published_collab
WHERE workspace_id = (SELECT workspace_id FROM af_workspace WHERE publish_namespace = $1)
AND publish_name = $2
"#,
publish_namespace,
publish_name,
)
.fetch_one(executor)
.await?;
Ok(key)
}
#[inline]
pub async fn select_published_collab_blob<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
publish_namespace: &str,
publish_name: &str,
) -> Result<Vec<u8>, AppError> {
let res = sqlx::query_scalar!(
r#"
SELECT blob
FROM af_published_collab
WHERE workspace_id = (SELECT workspace_id FROM af_workspace WHERE publish_namespace = $1)
AND publish_name = $2
"#,
publish_namespace,
publish_name,
)
.fetch_one(executor)
.await?;
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_for_view_ids<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
view_ids: &[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.view_id = ANY($1);
"#,
view_ids,
)
.fetch_all(executor)
.await?;
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
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.view_id = $1;
"#,
view_id,
)
.fetch_one(executor)
.await?;
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,
) -> Result<Uuid, AppError> {
let res = sqlx::query!(
r#"
SELECT workspace_id
FROM af_workspace
WHERE publish_namespace = $1
"#,
publish_namespace,
)
.fetch_one(executor)
.await?;
Ok(res.workspace_id)
}
pub async fn select_published_view_ids_for_workspace<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: Uuid,
) -> Result<Vec<Uuid>, AppError> {
let res = sqlx::query_scalar!(
r#"
SELECT view_id
FROM af_published_collab
WHERE workspace_id = $1
"#,
workspace_id,
)
.fetch_all(executor)
.await?;
Ok(res)
}