feat: publish namespace and metadata

This commit is contained in:
Zack Fu Zi Xiang 2024-06-14 21:35:39 +08:00
parent ee16f428c9
commit 18da7b873b
No known key found for this signature in database
5 changed files with 211 additions and 12 deletions

View File

@ -278,6 +278,11 @@ pub struct CollabMemberIdentify {
pub object_id: String,
}
#[derive(Deserialize)]
pub struct UpdatePublishNamespace {
pub new_namespace: String,
}
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
pub struct QueryCollabMembers {
#[validate(custom = "validate_not_empty_str")]

View File

@ -790,3 +790,64 @@ pub async fn upsert_workspace_settings(
Ok(())
}
#[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 insert_or_replace_publish_collab_meta<'a, E: Executor<'a, Database = Postgres>>(
executor: E,
workspace_id: &Uuid,
doc_name: &str,
publisher_uuid: &Uuid,
metadata: &serde_json::Value,
) -> Result<(), AppError> {
let res = sqlx::query!(
r#"
INSERT INTO af_published_collab (doc_name, published_by, workspace_id, metadata)
VALUES ($1, (SELECT uid FROM af_user WHERE uuid = $2), $3, $4)
ON CONFLICT (workspace_id, doc_name) DO UPDATE
SET metadata = $4
"#,
doc_name,
publisher_uuid,
workspace_id,
metadata
)
.execute(executor)
.await?;
if res.rows_affected() != 1 {
tracing::error!(
"Failed to insert or replace publish collab meta, workspace_id: {}, doc_name: {}, publisher_uuid: {}, rows_affected: {}",
workspace_id, doc_name, publisher_uuid, res.rows_affected()
);
}
Ok(())
}

View File

@ -0,0 +1,28 @@
-- stores the published view of a workspace by a user of workspace
CREATE TABLE IF NOT EXISTS af_published_collab (
doc_name TEXT NOT NULL,
published_by BIGINT NOT NULL REFERENCES af_user(uid) ON DELETE CASCADE,
workspace_id UUID NOT NULL REFERENCES af_workspace(workspace_id) ON DELETE CASCADE,
metadata JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (workspace_id, doc_name)
);
-- trigger to update updated_at column
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER af_published_collab_update_updated_at
BEFORE UPDATE ON af_published_collab
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();
-- every workspace have a prefix for published view
ALTER TABLE af_workspace ADD COLUMN publish_namespace TEXT UNIQUE;

View File

@ -128,6 +128,14 @@ pub fn workspace_scope() -> Scope {
.route(web::put().to(update_collab_member_handler))
.route(web::delete().to(remove_collab_member_handler)),
)
.service(
web::resource("/{workspace_id}/publish-namespace")
.route(web::put().to(put_publish_namespace_handler))
)
.service(
web::resource("/{workspace_id}/collab/{object_id}/publish")
.route(web::post().to(post_publish_collab_handler))
)
.service(
web::resource("/{workspace_id}/collab/{object_id}/member/list")
.route(web::get().to(get_collab_member_list_handler)),
@ -937,6 +945,43 @@ async fn remove_collab_member_handler(
Ok(Json(AppResponse::Ok()))
}
async fn put_publish_namespace_handler(
user_uuid: UserUuid,
workspace_id: web::Path<Uuid>,
payload: Json<UpdatePublishNamespace>,
state: Data<AppState>,
) -> Result<Json<AppResponse<()>>> {
let workspace_id = workspace_id.into_inner();
let new_namespace = payload.into_inner().new_namespace;
biz::workspace::ops::update_workspace_namespace(
&state.pg_pool,
&user_uuid,
&workspace_id,
&new_namespace,
)
.await?;
Ok(Json(AppResponse::Ok()))
}
async fn post_publish_collab_handler(
path_param: web::Path<(Uuid, String)>,
user_uuid: UserUuid,
payload: String,
state: Data<AppState>,
) -> Result<Json<AppResponse<()>>> {
let (workspace_id, doc_name) = path_param.into_inner();
let metadata = serde_json::Value::from(payload);
biz::workspace::ops::publish_workspace_collab(
&state.pg_pool,
&workspace_id,
&doc_name,
&user_uuid,
&metadata,
)
.await?;
Ok(Json(AppResponse::Ok()))
}
#[instrument(level = "debug", skip(state, payload), err)]
async fn get_collab_member_list_handler(
payload: Json<QueryCollabMembers>,

View File

@ -18,12 +18,12 @@ use database::resource_usage::get_all_workspace_blob_metadata;
use database::user::select_uid_from_email;
use database::workspace::{
change_workspace_icon, delete_from_workspace, delete_workspace_members, get_invitation_by_id,
insert_user_workspace, insert_workspace_invitation, rename_workspace, select_all_user_workspaces,
select_user_is_workspace_owner, select_workspace, select_workspace_invitations_for_user,
select_workspace_member, select_workspace_member_list, select_workspace_settings,
select_workspace_total_collab_bytes, update_updated_at_of_workspace,
update_workspace_invitation_set_status_accepted, upsert_workspace_member,
upsert_workspace_member_with_txn, upsert_workspace_settings,
insert_or_replace_publish_collab_meta, insert_user_workspace, insert_workspace_invitation,
rename_workspace, select_all_user_workspaces, select_user_is_workspace_owner, select_workspace,
select_workspace_invitations_for_user, select_workspace_member, select_workspace_member_list,
select_workspace_settings, select_workspace_total_collab_bytes, update_updated_at_of_workspace,
update_workspace_invitation_set_status_accepted, update_workspace_publish_namespace,
upsert_workspace_member, upsert_workspace_member_with_txn, upsert_workspace_settings,
};
use database_entity::dto::{
AFAccessLevel, AFRole, AFWorkspace, AFWorkspaceInvitation, AFWorkspaceInvitationStatus,
@ -114,6 +114,38 @@ pub async fn patch_workspace(
Ok(())
}
pub async fn update_workspace_namespace(
pg_pool: &PgPool,
user_uuid: &Uuid,
workspace_id: &Uuid,
new_namespace: &str,
) -> Result<(), AppError> {
check_workspace_owner(pg_pool, user_uuid, workspace_id).await?;
check_workspace_namespace(new_namespace).await?;
update_workspace_publish_namespace(pg_pool, workspace_id, new_namespace).await?;
Ok(())
}
pub async fn publish_workspace_collab(
pg_pool: &PgPool,
workspace_id: &Uuid,
doc_name: &str,
publisher_uuid: &Uuid,
metadata: &serde_json::Value,
) -> Result<(), AppError> {
insert_or_replace_publish_collab_meta(pg_pool, workspace_id, doc_name, publisher_uuid, metadata)
.await?;
Ok(())
}
pub async fn publish_workspace_collab_data(
_pg_pool: &PgPool,
_workspace_id: &Uuid,
_view_id: &Uuid,
) -> Result<(), AppError> {
Ok(())
}
pub async fn get_all_user_workspaces(
pg_pool: &PgPool,
user_uuid: &Uuid,
@ -453,12 +485,7 @@ pub async fn get_workspace_document_total_bytes(
user_uuid: &Uuid,
workspace_id: &Uuid,
) -> Result<WorkspaceUsage, AppError> {
let is_owner = select_user_is_workspace_owner(pg_pool, user_uuid, workspace_id).await?;
if !is_owner {
return Err(AppError::UserUnAuthorized(
"User is not the owner of the workspace".to_string(),
));
}
check_workspace_owner(pg_pool, user_uuid, workspace_id).await?;
let byte_count = select_workspace_total_collab_bytes(pg_pool, workspace_id).await?;
Ok(WorkspaceUsage {
@ -510,3 +537,36 @@ pub async fn update_workspace_settings(
tx.commit().await?;
Ok(())
}
async fn check_workspace_owner(
pg_pool: &PgPool,
user_uuid: &Uuid,
workspace_id: &Uuid,
) -> Result<(), AppError> {
match select_user_is_workspace_owner(pg_pool, user_uuid, workspace_id).await? {
true => Ok(()),
false => Err(AppError::UserUnAuthorized(
"User is not the owner of the workspace".to_string(),
)),
}
}
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(),
));
}
// Only contain alphanumeric characters and hyphens
for c in new_namespace.chars() {
if !c.is_alphanumeric() && c != '-' {
return Err(AppError::InvalidRequest(
"Namespace must only contain alphanumeric characters and hyphens".to_string(),
));
}
}
Ok(())
}