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,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdatePublishNamespace {
|
||||
pub new_namespace: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
||||
pub struct QueryCollabMembers {
|
||||
#[validate(custom = "validate_not_empty_str")]
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::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>,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue