feat: publish namespace and metadata
This commit is contained in:
parent
ee16f428c9
commit
18da7b873b
|
|
@ -278,6 +278,11 @@ pub struct CollabMemberIdentify {
|
||||||
pub object_id: String,
|
pub object_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct UpdatePublishNamespace {
|
||||||
|
pub new_namespace: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
||||||
pub struct QueryCollabMembers {
|
pub struct QueryCollabMembers {
|
||||||
#[validate(custom = "validate_not_empty_str")]
|
#[validate(custom = "validate_not_empty_str")]
|
||||||
|
|
|
||||||
|
|
@ -790,3 +790,64 @@ pub async fn upsert_workspace_settings(
|
||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -128,6 +128,14 @@ pub fn workspace_scope() -> Scope {
|
||||||
.route(web::put().to(update_collab_member_handler))
|
.route(web::put().to(update_collab_member_handler))
|
||||||
.route(web::delete().to(remove_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(
|
.service(
|
||||||
web::resource("/{workspace_id}/collab/{object_id}/member/list")
|
web::resource("/{workspace_id}/collab/{object_id}/member/list")
|
||||||
.route(web::get().to(get_collab_member_list_handler)),
|
.route(web::get().to(get_collab_member_list_handler)),
|
||||||
|
|
@ -937,6 +945,43 @@ async fn remove_collab_member_handler(
|
||||||
Ok(Json(AppResponse::Ok()))
|
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)]
|
#[instrument(level = "debug", skip(state, payload), err)]
|
||||||
async fn get_collab_member_list_handler(
|
async fn get_collab_member_list_handler(
|
||||||
payload: Json<QueryCollabMembers>,
|
payload: Json<QueryCollabMembers>,
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ use database::resource_usage::get_all_workspace_blob_metadata;
|
||||||
use database::user::select_uid_from_email;
|
use database::user::select_uid_from_email;
|
||||||
use database::workspace::{
|
use database::workspace::{
|
||||||
change_workspace_icon, delete_from_workspace, delete_workspace_members, get_invitation_by_id,
|
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,
|
insert_or_replace_publish_collab_meta, insert_user_workspace, insert_workspace_invitation,
|
||||||
select_user_is_workspace_owner, select_workspace, select_workspace_invitations_for_user,
|
rename_workspace, select_all_user_workspaces, select_user_is_workspace_owner, select_workspace,
|
||||||
select_workspace_member, select_workspace_member_list, select_workspace_settings,
|
select_workspace_invitations_for_user, select_workspace_member, select_workspace_member_list,
|
||||||
select_workspace_total_collab_bytes, update_updated_at_of_workspace,
|
select_workspace_settings, select_workspace_total_collab_bytes, update_updated_at_of_workspace,
|
||||||
update_workspace_invitation_set_status_accepted, upsert_workspace_member,
|
update_workspace_invitation_set_status_accepted, update_workspace_publish_namespace,
|
||||||
upsert_workspace_member_with_txn, upsert_workspace_settings,
|
upsert_workspace_member, upsert_workspace_member_with_txn, upsert_workspace_settings,
|
||||||
};
|
};
|
||||||
use database_entity::dto::{
|
use database_entity::dto::{
|
||||||
AFAccessLevel, AFRole, AFWorkspace, AFWorkspaceInvitation, AFWorkspaceInvitationStatus,
|
AFAccessLevel, AFRole, AFWorkspace, AFWorkspaceInvitation, AFWorkspaceInvitationStatus,
|
||||||
|
|
@ -114,6 +114,38 @@ pub async fn patch_workspace(
|
||||||
Ok(())
|
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(
|
pub async fn get_all_user_workspaces(
|
||||||
pg_pool: &PgPool,
|
pg_pool: &PgPool,
|
||||||
user_uuid: &Uuid,
|
user_uuid: &Uuid,
|
||||||
|
|
@ -453,12 +485,7 @@ pub async fn get_workspace_document_total_bytes(
|
||||||
user_uuid: &Uuid,
|
user_uuid: &Uuid,
|
||||||
workspace_id: &Uuid,
|
workspace_id: &Uuid,
|
||||||
) -> Result<WorkspaceUsage, AppError> {
|
) -> Result<WorkspaceUsage, AppError> {
|
||||||
let is_owner = select_user_is_workspace_owner(pg_pool, user_uuid, workspace_id).await?;
|
check_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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let byte_count = select_workspace_total_collab_bytes(pg_pool, workspace_id).await?;
|
let byte_count = select_workspace_total_collab_bytes(pg_pool, workspace_id).await?;
|
||||||
Ok(WorkspaceUsage {
|
Ok(WorkspaceUsage {
|
||||||
|
|
@ -510,3 +537,36 @@ pub async fn update_workspace_settings(
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue