mirror of
https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
synced 2025-04-19 03:24:42 -04:00
feat: client-api integration: save user meta data (#133)
* chore: update * feat: get user workspace info * feat: return list of workspace * feat: return latest workspace id * feat: latest workspace id * test: add tests
This commit is contained in:
parent
d0d2e916a7
commit
7c503372e0
24 changed files with 579 additions and 125 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT * FROM public.af_workspace WHERE owner_uid = (\n SELECT uid FROM public.af_user WHERE uuid = $1\n )\n ",
|
||||
"query": "\n SELECT * FROM public.af_workspace WHERE owner_uid = (\n SELECT uid FROM public.af_user WHERE uuid = $1\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -46,13 +46,13 @@
|
|||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d37119628f436d151a5559e692a3d6a7a6bc3c8226631ce6a0bcd0c7f1c0a8c5"
|
||||
"hash": "030b315f14742d266f545d6db37cc8cb083f9d52ebecd252311c4faf6fb5ab22"
|
||||
}
|
58
.sqlx/query-5855567a45f990e03f9249e0c591ebb8c9949a9b7e5bec114535c99f89a2a1dd.json
generated
Normal file
58
.sqlx/query-5855567a45f990e03f9249e0c591ebb8c9949a9b7e5bec114535c99f89a2a1dd.json
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT w.* \n FROM af_workspace w\n JOIN af_workspace_member wm ON w.workspace_id = wm.workspace_id\n WHERE wm.uid = (\n SELECT uid FROM public.af_user WHERE uuid = $1\n );\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "database_storage_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "owner_uid",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "workspace_type",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "deleted_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "workspace_name",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5855567a45f990e03f9249e0c591ebb8c9949a9b7e5bec114535c99f89a2a1dd"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM public.af_workspace_member\n WHERE \n workspace_id = $1 \n AND uid = (\n SELECT uid FROM public.af_user WHERE email = $2\n )\n -- Ensure the user to be deleted is not the original owner\n AND uid <> (\n SELECT owner_uid FROM public.af_workspace WHERE workspace_id = $1\n );\n ",
|
||||
"query": "\n DELETE FROM public.af_workspace_member\n WHERE \n workspace_id = $1 \n AND uid = (\n SELECT uid FROM public.af_user WHERE email = $2\n )\n -- Ensure the user to be deleted is not the original owner. \n -- 1. TODO(nathan): User must transfer ownership to another user first.\n -- 2. User must have at least one workspace\n AND uid <> (\n SELECT owner_uid FROM public.af_workspace WHERE workspace_id = $1\n );\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
|
@ -11,5 +11,5 @@
|
|||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "54c2e86c48a1549d8006bd5592eb5610985d5d43cd67efd417a0c6e52a967dfd"
|
||||
"hash": "69ab5d0cc26ae1830849de97d53731dbddbf4189cca04706928848e932b7a72c"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT *\n FROM public.af_user_profile_view WHERE uuid = $1\n ",
|
||||
"query": "\n SELECT *\n FROM public.af_user_profile_view WHERE uuid = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -78,5 +78,5 @@
|
|||
true
|
||||
]
|
||||
},
|
||||
"hash": "a4610ec5cb6fdccf7ef07a0526d35b7659a7e953b47b1e7b87188fa4b7277b1f"
|
||||
"hash": "7438ddaf9ed9fab7ba4cb1b9c79ff541fa2e63a629cad4f8e9692a50a4e5e03c"
|
||||
}
|
58
.sqlx/query-af5c5c1fbf22870171f644e70b0abe5a46d40b9bff68df3896adc5348539d27b.json
generated
Normal file
58
.sqlx/query-af5c5c1fbf22870171f644e70b0abe5a46d40b9bff68df3896adc5348539d27b.json
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT * FROM public.af_workspace WHERE workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "database_storage_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "owner_uid",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "workspace_type",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "deleted_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "workspace_name",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "af5c5c1fbf22870171f644e70b0abe5a46d40b9bff68df3896adc5348539d27b"
|
||||
}
|
15
.sqlx/query-d921f52e4bc3fef72c810e19455a2fa4fbd52f5a1f3a1838b146d001eadabd47.json
generated
Normal file
15
.sqlx/query-d921f52e4bc3fef72c810e19455a2fa4fbd52f5a1f3a1838b146d001eadabd47.json
generated
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE af_workspace_member\n SET updated_at = CURRENT_TIMESTAMP\n WHERE uid = (SELECT uid FROM public.af_user WHERE uuid = $1) AND workspace_id = $2;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d921f52e4bc3fef72c810e19455a2fa4fbd52f5a1f3a1838b146d001eadabd47"
|
||||
}
|
|
@ -2,10 +2,10 @@ use crate::notify::{ClientToken, TokenStateReceiver};
|
|||
use anyhow::{anyhow, Context};
|
||||
use bytes::Bytes;
|
||||
use database_entity::dto::{
|
||||
AFBlobMetadata, AFBlobRecord, AFCollabMember, AFCollabMembers, AFUserProfile, AFWorkspaceMember,
|
||||
AFWorkspaces, BatchQueryCollabParams, BatchQueryCollabResult, CollabMemberIdentify,
|
||||
DeleteCollabParams, InsertCollabMemberParams, InsertCollabParams, QueryCollabMembers,
|
||||
QueryCollabParams, RawData, UpdateCollabMemberParams,
|
||||
AFBlobMetadata, AFBlobRecord, AFCollabMember, AFCollabMembers, AFUserProfile,
|
||||
AFUserWorkspaceInfo, AFWorkspace, AFWorkspaceMember, AFWorkspaces, BatchQueryCollabParams,
|
||||
BatchQueryCollabResult, CollabMemberIdentify, DeleteCollabParams, InsertCollabMemberParams,
|
||||
InsertCollabParams, QueryCollabMembers, QueryCollabParams, RawData, UpdateCollabMemberParams,
|
||||
};
|
||||
use futures_util::StreamExt;
|
||||
use gotrue::grant::Grant;
|
||||
|
@ -335,6 +335,19 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn get_user_workspace_info(&self) -> Result<AFUserWorkspaceInfo, AppError> {
|
||||
let url = format!("{}/api/user/workspace", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<AFUserWorkspaceInfo>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn get_workspaces(&self) -> Result<AFWorkspaces, AppError> {
|
||||
let url = format!("{}/api/workspace/list", self.base_url);
|
||||
|
@ -348,12 +361,25 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn open_workspace(&self, workspace_id: &str) -> Result<AFWorkspace, AppError> {
|
||||
let url = format!("{}/api/workspace/{}/open", self.base_url, workspace_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::PUT, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<AFWorkspace>::from_response(resp)
|
||||
.await?
|
||||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn get_workspace_members(
|
||||
&self,
|
||||
workspace_uuid: Uuid,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<AFWorkspaceMember>, AppError> {
|
||||
let url = format!("{}/api/workspace/{}/member", self.base_url, workspace_uuid);
|
||||
let url = format!("{}/api/workspace/{}/member", self.base_url, workspace_id);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::pg_row::{AFBlobMetadataRow, AFUserProfileRow, AFWorkspaceMemberRow, AFWorkspaceRows};
|
||||
use crate::error::DatabaseError;
|
||||
use crate::pg_row::{AFBlobMetadataRow, AFUserProfileRow, AFWorkspaceMemberRow, AFWorkspaceRow};
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collab_entity::CollabType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -6,6 +8,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
|||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
use validator::{Validate, ValidationError};
|
||||
|
||||
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
||||
|
@ -307,9 +310,94 @@ pub struct AFCollabMembers(pub Vec<AFCollabMember>);
|
|||
|
||||
pub type RawData = Vec<u8>;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AFUserProfile {
|
||||
pub uid: i64,
|
||||
pub uuid: Uuid,
|
||||
pub email: Option<String>,
|
||||
pub password: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
pub encryption_sign: Option<String>,
|
||||
pub latest_workspace_id: Uuid,
|
||||
}
|
||||
|
||||
impl TryFrom<AFUserProfileRow> for AFUserProfile {
|
||||
type Error = DatabaseError;
|
||||
|
||||
fn try_from(value: AFUserProfileRow) -> Result<Self, Self::Error> {
|
||||
let uid = value
|
||||
.uid
|
||||
.ok_or(DatabaseError::Internal(anyhow!("Unexpect empty uid")))?;
|
||||
let uuid = value
|
||||
.uuid
|
||||
.ok_or(DatabaseError::Internal(anyhow!("Unexpect empty uuid")))?;
|
||||
let latest_workspace_id = value
|
||||
.latest_workspace_id
|
||||
.ok_or(DatabaseError::Internal(anyhow!(
|
||||
"Unexpect empty latest_workspace_id"
|
||||
)))?;
|
||||
Ok(Self {
|
||||
uid,
|
||||
uuid,
|
||||
email: value.email,
|
||||
password: value.password,
|
||||
name: value.name,
|
||||
metadata: value.metadata,
|
||||
encryption_sign: value.encryption_sign,
|
||||
latest_workspace_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AFWorkspace {
|
||||
pub workspace_id: Uuid,
|
||||
pub database_storage_id: Uuid,
|
||||
pub owner_uid: i64,
|
||||
pub workspace_type: i32,
|
||||
pub workspace_name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl TryFrom<AFWorkspaceRow> for AFWorkspace {
|
||||
type Error = DatabaseError;
|
||||
|
||||
fn try_from(value: AFWorkspaceRow) -> Result<Self, Self::Error> {
|
||||
let owner_uid = value
|
||||
.owner_uid
|
||||
.ok_or(DatabaseError::Internal(anyhow!("Unexpect empty owner_uid")))?;
|
||||
let database_storage_id = value
|
||||
.database_storage_id
|
||||
.ok_or(DatabaseError::Internal(anyhow!(
|
||||
"Unexpect empty workspace_id"
|
||||
)))?;
|
||||
|
||||
let workspace_name = value.workspace_name.unwrap_or_default();
|
||||
let created_at = value.created_at.unwrap_or_else(Utc::now);
|
||||
|
||||
Ok(Self {
|
||||
workspace_id: value.workspace_id,
|
||||
database_storage_id,
|
||||
owner_uid,
|
||||
workspace_type: value.workspace_type,
|
||||
workspace_name,
|
||||
created_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AFWorkspaces(pub Vec<AFWorkspace>);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AFUserWorkspaceInfo {
|
||||
pub user_profile: AFUserProfile,
|
||||
pub visiting_workspace: AFWorkspace,
|
||||
pub workspaces: Vec<AFWorkspace>,
|
||||
}
|
||||
|
||||
/// ***************************************************************
|
||||
/// Make alias for the database entity. Hiding the Sqlx Rows type.
|
||||
pub type AFUserProfile = AFUserProfileRow;
|
||||
pub type AFWorkspaces = AFWorkspaceRows;
|
||||
pub type AFWorkspaceMember = AFWorkspaceMemberRow;
|
||||
pub type AFBlobMetadata = AFBlobMetadataRow;
|
||||
|
|
|
@ -2,7 +2,6 @@ use crate::dto::AFRole;
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use std::ops::Deref;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
|
@ -31,31 +30,6 @@ pub struct AFUserProfileRow {
|
|||
pub latest_workspace_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow, Deserialize, Serialize)]
|
||||
pub struct AFWorkspaceRows(pub Vec<AFWorkspaceRow>);
|
||||
|
||||
impl Deref for AFWorkspaceRows {
|
||||
type Target = Vec<AFWorkspaceRow>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<AFWorkspaceRow>> for AFWorkspaceRows {
|
||||
fn from(v: Vec<AFWorkspaceRow>) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl AFWorkspaceRows {
|
||||
pub fn get_latest(&self, profile: &AFUserProfileRow) -> Option<AFWorkspaceRow> {
|
||||
match profile.latest_workspace_id {
|
||||
Some(ws_id) => self.0.iter().find(|ws| ws.workspace_id == ws_id).cloned(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(FromRow, Serialize, Deserialize)]
|
||||
pub struct AFWorkspaceMemberRow {
|
||||
pub email: String,
|
||||
|
|
|
@ -10,24 +10,6 @@ use crate::user::select_uid_from_email;
|
|||
use database_entity::error::DatabaseError;
|
||||
use database_entity::pg_row::{AFUserProfileRow, AFWorkspaceMemberRow, AFWorkspaceRow};
|
||||
|
||||
pub async fn select_all_workspaces_owned(
|
||||
pool: &PgPool,
|
||||
owner_uuid: &Uuid,
|
||||
) -> Result<Vec<AFWorkspaceRow>, DatabaseError> {
|
||||
let workspaces = sqlx::query_as!(
|
||||
AFWorkspaceRow,
|
||||
r#"
|
||||
SELECT * FROM public.af_workspace WHERE owner_uid = (
|
||||
SELECT uid FROM public.af_user WHERE uuid = $1
|
||||
)
|
||||
"#,
|
||||
owner_uuid
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
Ok(workspaces)
|
||||
}
|
||||
|
||||
/// Checks whether a user, identified by a UUID, is an 'Owner' of a workspace, identified by its
|
||||
/// workspace_id.
|
||||
pub async fn select_user_is_workspace_owner(
|
||||
|
@ -235,7 +217,9 @@ pub async fn delete_workspace_members(
|
|||
AND uid = (
|
||||
SELECT uid FROM public.af_user WHERE email = $2
|
||||
)
|
||||
-- Ensure the user to be deleted is not the original owner
|
||||
-- Ensure the user to be deleted is not the original owner.
|
||||
-- 1. TODO(nathan): User must transfer ownership to another user first.
|
||||
-- 2. User must have at least one workspace
|
||||
AND uid <> (
|
||||
SELECT owner_uid FROM public.af_workspace WHERE workspace_id = $1
|
||||
);
|
||||
|
@ -293,19 +277,93 @@ pub async fn select_workspace_member(
|
|||
Ok(member)
|
||||
}
|
||||
|
||||
pub async fn select_user_profile_view_by_uuid(
|
||||
pool: &PgPool,
|
||||
pub async fn select_user_profile<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
user_uuid: &Uuid,
|
||||
) -> Result<Option<AFUserProfileRow>, DatabaseError> {
|
||||
let user_profile = sqlx::query_as!(
|
||||
AFUserProfileRow,
|
||||
r#"
|
||||
SELECT *
|
||||
FROM public.af_user_profile_view WHERE uuid = $1
|
||||
"#,
|
||||
SELECT *
|
||||
FROM public.af_user_profile_view WHERE uuid = $1
|
||||
"#,
|
||||
user_uuid
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
pub async fn select_workspace<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<AFWorkspaceRow, DatabaseError> {
|
||||
let workspace = sqlx::query_as!(
|
||||
AFWorkspaceRow,
|
||||
r#"
|
||||
SELECT * FROM public.af_workspace WHERE workspace_id = $1
|
||||
"#,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
Ok(workspace)
|
||||
}
|
||||
pub async fn update_updated_at_of_workspace<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
user_uuid: &Uuid,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE af_workspace_member
|
||||
SET updated_at = CURRENT_TIMESTAMP
|
||||
WHERE uid = (SELECT uid FROM public.af_user WHERE uuid = $1) AND workspace_id = $2;
|
||||
"#,
|
||||
user_uuid,
|
||||
workspace_id
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a list of workspaces that the user is a member of.
|
||||
pub async fn select_user_workspace<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
user_uuid: &Uuid,
|
||||
) -> Result<Vec<AFWorkspaceRow>, DatabaseError> {
|
||||
let workspaces = sqlx::query_as!(
|
||||
AFWorkspaceRow,
|
||||
r#"
|
||||
SELECT w.*
|
||||
FROM af_workspace w
|
||||
JOIN af_workspace_member wm ON w.workspace_id = wm.workspace_id
|
||||
WHERE wm.uid = (
|
||||
SELECT uid FROM public.af_user WHERE uuid = $1
|
||||
);
|
||||
"#,
|
||||
user_uuid
|
||||
)
|
||||
.fetch_all(executor)
|
||||
.await?;
|
||||
Ok(workspaces)
|
||||
}
|
||||
|
||||
pub async fn select_all_user_workspaces(
|
||||
pool: &PgPool,
|
||||
owner_uuid: &Uuid,
|
||||
) -> Result<Vec<AFWorkspaceRow>, DatabaseError> {
|
||||
let workspaces = sqlx::query_as!(
|
||||
AFWorkspaceRow,
|
||||
r#"
|
||||
SELECT * FROM public.af_workspace WHERE owner_uid = (
|
||||
SELECT uid FROM public.af_user WHERE uuid = $1
|
||||
)
|
||||
"#,
|
||||
owner_uuid
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
Ok(workspaces)
|
||||
}
|
||||
|
|
|
@ -49,12 +49,10 @@ impl WorkspaceMemberChangeset {
|
|||
name: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_role(mut self, role: AFRole) -> Self {
|
||||
self.role = Some(role);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_name(mut self, name: String) -> Self {
|
||||
self.name = Some(name);
|
||||
self
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
-- af_workspace contains all the workspaces. Each workspace contains a list of members defined in af_workspace_member
|
||||
CREATE TABLE IF NOT EXISTS af_workspace (
|
||||
workspace_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
database_storage_id UUID DEFAULT uuid_generate_v4(),
|
||||
owner_uid BIGINT REFERENCES af_user(uid) ON DELETE CASCADE,
|
||||
workspace_id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
database_storage_id UUID NOT NULL DEFAULT uuid_generate_v4(),
|
||||
owner_uid BIGINT NOT NULL REFERENCES af_user(uid) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
-- 0: Free
|
||||
workspace_type INTEGER NOT NULL DEFAULT 0,
|
||||
|
|
|
@ -17,7 +17,8 @@ use actix_web::web::{Data, Json};
|
|||
use actix_web::HttpRequest;
|
||||
use actix_web::Result;
|
||||
use actix_web::{web, HttpResponse, Scope};
|
||||
use database_entity::pg_row::AFUserProfileRow;
|
||||
use database_entity::dto::{AFUserProfile, AFUserWorkspaceInfo};
|
||||
|
||||
use tracing_actix_web::RequestId;
|
||||
|
||||
pub fn user_scope() -> Scope {
|
||||
|
@ -26,6 +27,7 @@ pub fn user_scope() -> Scope {
|
|||
.service(web::resource("/verify/{access_token}").route(web::get().to(verify_user_handler)))
|
||||
.service(web::resource("/update").route(web::post().to(update_user_handler)))
|
||||
.service(web::resource("/profile").route(web::get().to(get_user_profile_handler)))
|
||||
.service(web::resource("/workspace").route(web::get().to(get_user_workspace_info_handler)))
|
||||
|
||||
// deprecated
|
||||
.service(web::resource("/login").route(web::post().to(login_handler)))
|
||||
|
@ -38,7 +40,7 @@ pub fn user_scope() -> Scope {
|
|||
async fn verify_user_handler(
|
||||
path: web::Path<String>,
|
||||
state: Data<AppState>,
|
||||
required_id: RequestId,
|
||||
request_id: RequestId,
|
||||
) -> Result<JsonAppResponse<SignInTokenResponse>> {
|
||||
let access_token = path.into_inner();
|
||||
let is_new = biz::user::token_verify(&state.pg_pool, &state.gotrue_client, &access_token).await?;
|
||||
|
@ -50,18 +52,28 @@ async fn verify_user_handler(
|
|||
async fn get_user_profile_handler(
|
||||
uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
required_id: RequestId,
|
||||
) -> Result<JsonAppResponse<AFUserProfileRow>> {
|
||||
request_id: RequestId,
|
||||
) -> Result<JsonAppResponse<AFUserProfile>> {
|
||||
let profile = biz::user::get_profile(&state.pg_pool, &uuid).await?;
|
||||
Ok(AppResponse::Ok().with_data(profile).into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state), err)]
|
||||
async fn get_user_workspace_info_handler(
|
||||
uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
request_id: RequestId,
|
||||
) -> Result<JsonAppResponse<AFUserWorkspaceInfo>> {
|
||||
let info = biz::user::get_user_workspace_info(&state.pg_pool, &uuid).await?;
|
||||
Ok(AppResponse::Ok().with_data(info).into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state, auth, payload), err)]
|
||||
async fn update_user_handler(
|
||||
auth: Authorization,
|
||||
payload: Json<UpdateUserParams>,
|
||||
state: Data<AppState>,
|
||||
required_id: RequestId,
|
||||
request_id: RequestId,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
let params = payload.into_inner();
|
||||
biz::user::update_user(&state.pg_pool, auth.uuid()?, params).await?;
|
||||
|
|
|
@ -15,7 +15,7 @@ use shared_entity::data::{AppResponse, JsonAppResponse};
|
|||
use shared_entity::dto::workspace_dto::*;
|
||||
use shared_entity::error_code::ErrorCode;
|
||||
use sqlx::types::uuid;
|
||||
use tracing::{debug, instrument};
|
||||
use tracing::{debug, event, instrument};
|
||||
use tracing_actix_web::RequestId;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -25,6 +25,7 @@ pub const COLLAB_OBJECT_ID_PATH: &str = "object_id";
|
|||
pub fn workspace_scope() -> Scope {
|
||||
web::scope("/api/workspace")
|
||||
.service(web::resource("list").route(web::get().to(list_handler)))
|
||||
.service(web::resource("{workspace_id}/open").route(web::put().to(open_workspace_handler)))
|
||||
.service(
|
||||
web::resource("{workspace_id}/member")
|
||||
.route(web::get().to(get_workspace_members_handler))
|
||||
|
@ -62,13 +63,27 @@ async fn list_handler(
|
|||
uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<AFWorkspaces>> {
|
||||
let workspaces = workspace::ops::get_workspaces(&state.pg_pool, &uuid).await?;
|
||||
Ok(AppResponse::Ok().with_data(workspaces).into())
|
||||
let rows = workspace::ops::get_all_user_workspaces(&state.pg_pool, &uuid).await?;
|
||||
let workspaces = rows
|
||||
.into_iter()
|
||||
.flat_map(|row| {
|
||||
let result = AFWorkspace::try_from(row);
|
||||
if let Err(err) = &result {
|
||||
event!(
|
||||
tracing::Level::ERROR,
|
||||
"Failed to convert workspace row to AFWorkspace: {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
result
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(AppResponse::Ok().with_data(AFWorkspaces(workspaces)).into())
|
||||
}
|
||||
|
||||
#[instrument(skip(payload, state), err)]
|
||||
async fn add_workspace_members_handler(
|
||||
required_id: RequestId,
|
||||
request_id: RequestId,
|
||||
user_uuid: UserUuid,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
payload: Json<CreateWorkspaceMembers>,
|
||||
|
@ -140,6 +155,17 @@ async fn remove_workspace_member_handler(
|
|||
Ok(AppResponse::Ok().into())
|
||||
}
|
||||
|
||||
#[instrument(skip_all, err)]
|
||||
async fn open_workspace_handler(
|
||||
user_uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
workspace_id: web::Path<Uuid>,
|
||||
) -> Result<JsonAppResponse<AFWorkspace>> {
|
||||
let workspace_id = workspace_id.into_inner();
|
||||
let workspace = workspace::ops::open_workspace(&state.pg_pool, &user_uuid, &workspace_id).await?;
|
||||
Ok(AppResponse::Ok().with_data(workspace).into())
|
||||
}
|
||||
|
||||
#[instrument(skip_all, err)]
|
||||
async fn update_workspace_member_handler(
|
||||
payload: Json<WorkspaceMemberChangeset>,
|
||||
|
|
|
@ -319,7 +319,7 @@ where
|
|||
.get_collab_access_level(uid.into(), oid)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
to_database_error(
|
||||
app_err_to_database_error(
|
||||
err,
|
||||
format!(
|
||||
"failed to get the collab access level of user:{} for object:{}",
|
||||
|
@ -336,7 +336,7 @@ where
|
|||
.get_role_from_uid(uid, &workspace_id.parse()?)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
to_database_error(
|
||||
app_err_to_database_error(
|
||||
err,
|
||||
format!(
|
||||
"failed to get the role of the user:{} in workspace:{}",
|
||||
|
@ -347,7 +347,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn to_database_error(err: AppError, msg: String) -> DatabaseError {
|
||||
fn app_err_to_database_error(err: AppError, msg: String) -> DatabaseError {
|
||||
if err.is_record_not_found() {
|
||||
DatabaseError::RecordNotFound(msg)
|
||||
} else {
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
use anyhow::Result;
|
||||
use database::{user::create_user_if_not_exists, workspace::select_user_profile_view_by_uuid};
|
||||
use anyhow::{Context, Result};
|
||||
use database::{user::create_user_if_not_exists, workspace::select_user_profile};
|
||||
use gotrue::api::Client;
|
||||
use serde_json::json;
|
||||
use shared_entity::app_error::AppError;
|
||||
use std::ops::DerefMut;
|
||||
use uuid::Uuid;
|
||||
|
||||
use database_entity::pg_row::AFUserProfileRow;
|
||||
use database::workspace::{select_user_workspace, select_workspace};
|
||||
use database_entity::dto::{AFUserProfile, AFUserWorkspaceInfo, AFWorkspace};
|
||||
|
||||
use shared_entity::dto::auth_dto::UpdateUserParams;
|
||||
use sqlx::{types::uuid, PgPool};
|
||||
use tracing::instrument;
|
||||
|
||||
pub async fn token_verify(
|
||||
pg_pool: &PgPool,
|
||||
|
@ -21,16 +25,56 @@ pub async fn token_verify(
|
|||
Ok(is_new)
|
||||
}
|
||||
|
||||
pub async fn get_profile(
|
||||
pg_pool: &PgPool,
|
||||
uuid: &uuid::Uuid,
|
||||
) -> Result<AFUserProfileRow, AppError> {
|
||||
let profile = select_user_profile_view_by_uuid(pg_pool, uuid)
|
||||
pub async fn get_profile(pg_pool: &PgPool, uuid: &Uuid) -> Result<AFUserProfile, AppError> {
|
||||
let row = select_user_profile(pg_pool, uuid)
|
||||
.await?
|
||||
.ok_or(sqlx::Error::RowNotFound)?;
|
||||
|
||||
let profile = AFUserProfile::try_from(row)?;
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
#[instrument(skip(pg_pool), err)]
|
||||
pub async fn get_user_workspace_info(
|
||||
pg_pool: &PgPool,
|
||||
uuid: &Uuid,
|
||||
) -> Result<AFUserWorkspaceInfo, AppError> {
|
||||
let mut txn = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("failed to acquire the transaction to query the user workspace info")?;
|
||||
let row = select_user_profile(txn.deref_mut(), uuid)
|
||||
.await?
|
||||
.ok_or(sqlx::Error::RowNotFound)?;
|
||||
|
||||
// Get the latest workspace that the user has visited recently
|
||||
// TODO(nathan): the visiting_workspace might be None if the user get deleted from the workspace
|
||||
let visiting_workspace = AFWorkspace::try_from(
|
||||
select_workspace(txn.deref_mut(), &row.latest_workspace_id.unwrap()).await?,
|
||||
)?;
|
||||
|
||||
// Get the user profile
|
||||
let user_profile = AFUserProfile::try_from(row)?;
|
||||
|
||||
// Get all workspaces that the user can access to
|
||||
let workspaces = select_user_workspace(txn.deref_mut(), uuid)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|row| AFWorkspace::try_from(row).ok())
|
||||
.collect::<Vec<AFWorkspace>>();
|
||||
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("failed to commit the transaction to get user workspace info")?;
|
||||
|
||||
Ok(AFUserWorkspaceInfo {
|
||||
user_profile,
|
||||
visiting_workspace,
|
||||
workspaces,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_user(
|
||||
pg_pool: &PgPool,
|
||||
user_uuid: Uuid,
|
||||
|
|
|
@ -3,23 +3,46 @@ use anyhow::Context;
|
|||
use database::collab::upsert_collab_member_with_txn;
|
||||
use database::user::select_uid_from_email;
|
||||
use database::workspace::{
|
||||
delete_workspace_members, insert_workspace_member_with_txn, select_all_workspaces_owned,
|
||||
select_workspace_member_list, upsert_workspace_member,
|
||||
delete_workspace_members, insert_workspace_member_with_txn, select_all_user_workspaces,
|
||||
select_workspace, select_workspace_member_list, update_updated_at_of_workspace,
|
||||
upsert_workspace_member,
|
||||
};
|
||||
use database_entity::dto::{AFAccessLevel, AFRole};
|
||||
use database_entity::pg_row::{AFWorkspaceMemberRow, AFWorkspaceRows};
|
||||
use database_entity::dto::{AFAccessLevel, AFRole, AFWorkspace};
|
||||
use database_entity::pg_row::{AFWorkspaceMemberRow, AFWorkspaceRow};
|
||||
use shared_entity::app_error::AppError;
|
||||
use shared_entity::dto::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset};
|
||||
use sqlx::{types::uuid, PgPool};
|
||||
use std::ops::DerefMut;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn get_workspaces(
|
||||
pub async fn get_all_user_workspaces(
|
||||
pg_pool: &PgPool,
|
||||
user_uuid: &Uuid,
|
||||
) -> Result<AFWorkspaceRows, AppError> {
|
||||
let workspaces = select_all_workspaces_owned(pg_pool, user_uuid).await?;
|
||||
Ok(AFWorkspaceRows(workspaces))
|
||||
) -> Result<Vec<AFWorkspaceRow>, AppError> {
|
||||
let workspaces = select_all_user_workspaces(pg_pool, user_uuid).await?;
|
||||
Ok(workspaces)
|
||||
}
|
||||
|
||||
/// Returns the workspace with the given workspace_id and update the updated_at field of the
|
||||
/// workspace.
|
||||
pub async fn open_workspace(
|
||||
pg_pool: &PgPool,
|
||||
user_uuid: &Uuid,
|
||||
workspace_id: &Uuid,
|
||||
) -> Result<AFWorkspace, AppError> {
|
||||
let mut txn = pg_pool
|
||||
.begin()
|
||||
.await
|
||||
.context("Begin transaction to open workspace")?;
|
||||
let row = select_workspace(txn.deref_mut(), workspace_id).await?;
|
||||
update_updated_at_of_workspace(txn.deref_mut(), user_uuid, workspace_id).await?;
|
||||
txn
|
||||
.commit()
|
||||
.await
|
||||
.context("Commit transaction to open workspace")?;
|
||||
let workspace = AFWorkspace::try_from(row)?;
|
||||
|
||||
Ok(workspace)
|
||||
}
|
||||
|
||||
pub async fn add_workspace_members(
|
||||
|
|
|
@ -13,7 +13,7 @@ async fn collab_owner_permission_test() {
|
|||
let raw_data = "hello world".to_string().as_bytes().to_vec();
|
||||
let workspace_id = workspace_id_from_client(&c).await;
|
||||
let object_id = Uuid::new_v4().to_string();
|
||||
let uid = c.get_profile().await.unwrap().uid.unwrap();
|
||||
let uid = c.get_profile().await.unwrap().uid;
|
||||
|
||||
c.create_collab(InsertCollabParams::new(
|
||||
&object_id,
|
||||
|
@ -42,7 +42,7 @@ async fn update_collab_member_permission_test() {
|
|||
let raw_data = "hello world".to_string().as_bytes().to_vec();
|
||||
let workspace_id = workspace_id_from_client(&c).await;
|
||||
let object_id = Uuid::new_v4().to_string();
|
||||
let uid = c.get_profile().await.unwrap().uid.unwrap();
|
||||
let uid = c.get_profile().await.unwrap().uid;
|
||||
|
||||
c.create_collab(InsertCollabParams::new(
|
||||
&object_id,
|
||||
|
@ -91,7 +91,7 @@ async fn add_collab_member_test() {
|
|||
|
||||
// create new client
|
||||
let (c_2, _user) = generate_unique_registered_user_client().await;
|
||||
let uid_2 = c_2.get_profile().await.unwrap().uid.unwrap();
|
||||
let uid_2 = c_2.get_profile().await.unwrap().uid;
|
||||
|
||||
// add new member
|
||||
c_1
|
||||
|
@ -136,7 +136,7 @@ async fn add_collab_member_then_remove_test() {
|
|||
|
||||
// Create new client
|
||||
let (c_2, _user) = generate_unique_registered_user_client().await;
|
||||
let uid_2 = c_2.get_profile().await.unwrap().uid.unwrap();
|
||||
let uid_2 = c_2.get_profile().await.unwrap().uid;
|
||||
|
||||
// Add new member
|
||||
c_1
|
||||
|
|
|
@ -12,6 +12,7 @@ pub(crate) async fn workspace_id_from_client(c: &Client) -> String {
|
|||
c.get_workspaces()
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.workspace_id
|
||||
|
|
|
@ -10,8 +10,8 @@ async fn edit_workspace_without_permission() {
|
|||
let mut client_2 = TestClient::new_user().await;
|
||||
|
||||
let workspace_id = client_1.workspace_id().await;
|
||||
client_1.open_workspace(&workspace_id).await;
|
||||
client_2.open_workspace(&workspace_id).await;
|
||||
client_1.edit_workspace_collab(&workspace_id).await;
|
||||
client_2.edit_workspace_collab(&workspace_id).await;
|
||||
|
||||
client_1
|
||||
.collab_by_object_id
|
||||
|
@ -31,13 +31,13 @@ async fn init_sync_workspace_with_guest_permission() {
|
|||
let mut client_1 = TestClient::new_user().await;
|
||||
let mut client_2 = TestClient::new_user().await;
|
||||
let workspace_id = client_1.workspace_id().await;
|
||||
client_1.open_workspace(&workspace_id).await;
|
||||
client_1.edit_workspace_collab(&workspace_id).await;
|
||||
|
||||
// add client 2 as the member of the workspace then the client 2 will receive the update.
|
||||
client_1
|
||||
.add_workspace_member(&workspace_id, &client_2, AFRole::Guest)
|
||||
.await;
|
||||
client_2.open_workspace(&workspace_id).await;
|
||||
client_2.edit_workspace_collab(&workspace_id).await;
|
||||
|
||||
client_1
|
||||
.collab_by_object_id
|
||||
|
@ -57,7 +57,7 @@ async fn edit_workspace_with_guest_permission() {
|
|||
let mut client_1 = TestClient::new_user().await;
|
||||
let mut client_2 = TestClient::new_user().await;
|
||||
let workspace_id = client_1.workspace_id().await;
|
||||
client_1.open_workspace(&workspace_id).await;
|
||||
client_1.edit_workspace_collab(&workspace_id).await;
|
||||
|
||||
// add client 2 as the member of the workspace then the client 2 will receive the update.
|
||||
client_1
|
||||
|
@ -73,7 +73,7 @@ async fn edit_workspace_with_guest_permission() {
|
|||
.insert("name", "zack");
|
||||
client_1.wait_object_sync_complete(&workspace_id).await;
|
||||
|
||||
client_2.open_workspace(&workspace_id).await;
|
||||
client_2.edit_workspace_collab(&workspace_id).await;
|
||||
// make sure the client 2 has received the remote updates before the client 2 edits the collab
|
||||
client_2
|
||||
.wait_object_sync_complete_with_secs(&workspace_id, 10)
|
||||
|
|
|
@ -153,5 +153,5 @@ async fn admin_generate_link_and_user_sign_in() {
|
|||
assert!(is_new);
|
||||
|
||||
let workspaces = client.get_workspaces().await.unwrap();
|
||||
assert_eq!(workspaces.len(), 1);
|
||||
assert_eq!(workspaces.0.len(), 1);
|
||||
}
|
||||
|
|
|
@ -66,9 +66,7 @@ async fn sign_in_success() {
|
|||
|
||||
let workspaces = c.get_workspaces().await.unwrap();
|
||||
assert_eq!(workspaces.0.len(), 1);
|
||||
let profile = c.get_profile().await.unwrap();
|
||||
let latest_workspace = workspaces.get_latest(&profile);
|
||||
assert!(latest_workspace.is_some());
|
||||
let _ = c.get_profile().await.unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -8,8 +8,8 @@ use collab::core::origin::{CollabClient, CollabOrigin};
|
|||
use collab::preclude::Collab;
|
||||
use collab_entity::CollabType;
|
||||
use database_entity::dto::{
|
||||
AFAccessLevel, AFBlobMetadata, AFRole, InsertCollabMemberParams, QueryCollabParams,
|
||||
UpdateCollabMemberParams,
|
||||
AFAccessLevel, AFBlobMetadata, AFRole, AFUserWorkspaceInfo, AFWorkspace, AFWorkspaceMember,
|
||||
InsertCollabMemberParams, QueryCollabParams, UpdateCollabMemberParams,
|
||||
};
|
||||
use image::io::Reader as ImageReader;
|
||||
use serde_json::Value;
|
||||
|
@ -102,6 +102,14 @@ impl TestClient {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) async fn get_user_workspace_info(&self) -> AFUserWorkspaceInfo {
|
||||
self.api_client.get_user_workspace_info().await.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn open_workspace(&self, workspace_id: &str) -> AFWorkspace {
|
||||
self.api_client.open_workspace(workspace_id).await.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn try_update_workspace_member(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
|
@ -144,6 +152,14 @@ impl TestClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn get_workspace_members(&self, workspace_id: &str) -> Vec<AFWorkspaceMember> {
|
||||
self
|
||||
.api_client
|
||||
.get_workspace_members(workspace_id)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn add_client_as_collab_member(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
|
@ -253,6 +269,7 @@ impl TestClient {
|
|||
.get_workspaces()
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.first()
|
||||
.unwrap()
|
||||
.workspace_id
|
||||
|
@ -264,7 +281,7 @@ impl TestClient {
|
|||
}
|
||||
|
||||
pub(crate) async fn uid(&self) -> i64 {
|
||||
self.api_client.get_profile().await.unwrap().uid.unwrap()
|
||||
self.api_client.get_profile().await.unwrap().uid
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
|
@ -274,15 +291,13 @@ impl TestClient {
|
|||
collab_type: CollabType,
|
||||
) -> String {
|
||||
let object_id = Uuid::new_v4().to_string();
|
||||
let uid = self.api_client.get_profile().await.unwrap().uid.unwrap();
|
||||
|
||||
// Subscribe to object
|
||||
let handler = self
|
||||
.ws_client
|
||||
.subscribe(BusinessID::CollabId, object_id.clone())
|
||||
.unwrap();
|
||||
let (sink, stream) = (handler.sink(), handler.stream());
|
||||
let origin = CollabOrigin::Client(CollabClient::new(uid, self.device_id.clone()));
|
||||
let origin = CollabOrigin::Client(CollabClient::new(self.uid().await, self.device_id.clone()));
|
||||
let collab = Arc::new(MutexCollab::new(origin.clone(), &object_id, vec![]));
|
||||
|
||||
let ws_connect_state = self.ws_client.subscribe_connect_state();
|
||||
|
@ -309,7 +324,7 @@ impl TestClient {
|
|||
object_id
|
||||
}
|
||||
|
||||
pub(crate) async fn open_workspace(&mut self, workspace_id: &str) {
|
||||
pub(crate) async fn edit_workspace_collab(&mut self, workspace_id: &str) {
|
||||
self
|
||||
.open_collab(workspace_id, workspace_id, CollabType::Folder)
|
||||
.await;
|
||||
|
@ -322,15 +337,13 @@ impl TestClient {
|
|||
object_id: &str,
|
||||
collab_type: CollabType,
|
||||
) {
|
||||
let uid = self.api_client.get_profile().await.unwrap().uid.unwrap();
|
||||
|
||||
// Subscribe to object
|
||||
let handler = self
|
||||
.ws_client
|
||||
.subscribe(BusinessID::CollabId, object_id.to_string())
|
||||
.unwrap();
|
||||
let (sink, stream) = (handler.sink(), handler.stream());
|
||||
let origin = CollabOrigin::Client(CollabClient::new(uid, self.device_id.clone()));
|
||||
let origin = CollabOrigin::Client(CollabClient::new(self.uid().await, self.device_id.clone()));
|
||||
let collab = Arc::new(MutexCollab::new(origin.clone(), object_id, vec![]));
|
||||
|
||||
let ws_connect_state = self.ws_client.subscribe_connect_state();
|
||||
|
|
|
@ -156,6 +156,23 @@ async fn add_workspace_member_and_owner_then_delete_all() {
|
|||
assert_eq!(members[0].email, c1.email().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn workspace_owner_remove_self_from_workspace() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
let workspace_id = c1.workspace_id().await;
|
||||
|
||||
// the workspace owner can not remove 'self' from the workspace
|
||||
let error = c1
|
||||
.try_remove_workspace_member(&workspace_id, &c1)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(error.code, ErrorCode::NotEnoughPermissions);
|
||||
|
||||
let members = c1.get_workspace_members(&workspace_id).await;
|
||||
assert_eq!(members.len(), 1);
|
||||
assert_eq!(members[0].email, c1.email().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn workspace_second_owner_can_not_delete_origin_owner() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
|
@ -170,3 +187,48 @@ async fn workspace_second_owner_can_not_delete_origin_owner() {
|
|||
.unwrap_err();
|
||||
assert_eq!(error.code, ErrorCode::NotEnoughPermissions);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_workspace_info() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
let workspace_id = c1.workspace_id().await;
|
||||
let info = c1.get_user_workspace_info().await;
|
||||
assert_eq!(info.workspaces.len(), 1);
|
||||
assert_eq!(
|
||||
info.visiting_workspace.workspace_id.to_string(),
|
||||
workspace_id
|
||||
);
|
||||
|
||||
let c2 = TestClient::new_user_without_ws_conn().await;
|
||||
c1.add_workspace_member(&workspace_id, &c2, AFRole::Owner)
|
||||
.await;
|
||||
|
||||
// c2 should have 2 workspaces
|
||||
let info = c2.get_user_workspace_info().await;
|
||||
assert_eq!(info.workspaces.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_user_workspace_info_after_open_workspace() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
let workspace_id_c1 = c1.workspace_id().await;
|
||||
|
||||
let c2 = TestClient::new_user_without_ws_conn().await;
|
||||
c1.add_workspace_member(&workspace_id_c1, &c2, AFRole::Owner)
|
||||
.await;
|
||||
|
||||
let info = c2.get_user_workspace_info().await;
|
||||
let workspace_id_c2 = c1.workspace_id().await;
|
||||
assert_eq!(
|
||||
info.visiting_workspace.workspace_id.to_string(),
|
||||
workspace_id_c2
|
||||
);
|
||||
|
||||
// After open workspace, the visiting workspace should be the workspace that user just opened
|
||||
c2.open_workspace(&workspace_id_c1).await;
|
||||
let info = c2.get_user_workspace_info().await;
|
||||
assert_eq!(
|
||||
info.visiting_workspace.workspace_id.to_string(),
|
||||
workspace_id_c1
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue