mirror of
https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
synced 2025-04-19 03:24:42 -04:00
feat: api to get invitation code info
This commit is contained in:
parent
9ea3c46c26
commit
553d7101bd
10 changed files with 193 additions and 3 deletions
59
.sqlx/query-7c2d481530566aec6a6acf3f705d2a0ac6cd94c013c1639aa23f6401281b3fd9.json
generated
Normal file
59
.sqlx/query-7c2d481530566aec6a6acf3f705d2a0ac6cd94c013c1639aa23f6401281b3fd9.json
generated
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH invited_workspace_member AS (\n SELECT\n invite_code,\n COUNT(*) AS member_count,\n COUNT(CASE WHEN uid = $2 THEN uid END) > 0 AS is_member\n FROM af_workspace_invite_code\n JOIN af_workspace_member USING (workspace_id)\n WHERE invite_code = $1\n AND (expires_at IS NULL OR expires_at < NOW())\n GROUP BY invite_code\n )\n SELECT\n workspace_id,\n owner_profile.name AS \"owner_name!\",\n owner_profile.metadata ->> 'icon_url' AS owner_avatar,\n af_workspace.workspace_name AS \"workspace_name!\",\n af_workspace.icon AS workspace_icon_url,\n invited_workspace_member.member_count AS \"member_count!\",\n invited_workspace_member.is_member AS \"is_member!\"\n FROM af_workspace_invite_code\n JOIN af_workspace USING (workspace_id)\n JOIN af_user AS owner_profile ON af_workspace.owner_uid = owner_profile.uid\n JOIN invited_workspace_member USING (invite_code)\n WHERE invite_code = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "owner_name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "owner_avatar",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "workspace_name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "workspace_icon_url",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "member_count!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "is_member!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "7c2d481530566aec6a6acf3f705d2a0ac6cd94c013c1639aa23f6401281b3fd9"
|
||||
}
|
|
@ -193,6 +193,9 @@ pub enum AppError {
|
|||
|
||||
#[error("{0}")]
|
||||
FeatureNotAvailable(String),
|
||||
|
||||
#[error("unable to find invitation code")]
|
||||
InvalidInvitationCode,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
|
@ -276,6 +279,7 @@ impl AppError {
|
|||
AppError::ActionTimeout(_) => ErrorCode::ActionTimeout,
|
||||
AppError::InvalidBlock(_) => ErrorCode::InvalidBlock,
|
||||
AppError::FeatureNotAvailable(_) => ErrorCode::FeatureNotAvailable,
|
||||
AppError::InvalidInvitationCode => ErrorCode::InvalidInvitationCode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -453,6 +457,7 @@ pub enum ErrorCode {
|
|||
RequestTimeout = 1065,
|
||||
AIResponseError = 1066,
|
||||
FeatureNotAvailable = 1067,
|
||||
InvalidInvitationCode = 1068,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
|
|
@ -9,6 +9,8 @@ use client_api_entity::workspace_dto::TrashSectionItems;
|
|||
use client_api_entity::workspace_dto::{FolderView, QueryWorkspaceFolder, QueryWorkspaceParam};
|
||||
use client_api_entity::AuthProvider;
|
||||
use client_api_entity::CollabType;
|
||||
use client_api_entity::GetInvitationCodeInfoQuery;
|
||||
use client_api_entity::InvitationCodeInfo;
|
||||
use client_api_entity::InvitedWorkspace;
|
||||
use client_api_entity::JoinWorkspaceByInviteCodeParams;
|
||||
use client_api_entity::WorkspaceInviteCodeParams;
|
||||
|
@ -814,6 +816,22 @@ impl Client {
|
|||
process_response_data::<InvitedWorkspace>(resp).await
|
||||
}
|
||||
|
||||
pub async fn get_invitation_code_info(
|
||||
&self,
|
||||
invitation_code: &str,
|
||||
) -> Result<InvitationCodeInfo, AppResponseError> {
|
||||
let url = format!("{}/api/invite-code-info", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.query(&GetInvitationCodeInfoQuery {
|
||||
code: invitation_code.to_string(),
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
process_response_data::<InvitationCodeInfo>(resp).await
|
||||
}
|
||||
|
||||
pub async fn create_workspace_invitation_code(
|
||||
&self,
|
||||
workspace_id: &Uuid,
|
||||
|
|
|
@ -1247,6 +1247,22 @@ pub struct InvitedWorkspace {
|
|||
pub workspace_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetInvitationCodeInfoQuery {
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct InvitationCodeInfo {
|
||||
pub workspace_id: Uuid,
|
||||
pub workspace_name: String,
|
||||
pub owner_avatar: Option<String>,
|
||||
pub owner_name: String,
|
||||
pub workspace_icon_url: Option<String>,
|
||||
pub is_member: Option<bool>,
|
||||
pub member_count: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct JoinWorkspaceByInviteCodeParams {
|
||||
pub code: String,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use database_entity::dto::{
|
||||
AFRole, AFWorkspaceInvitation, AFWorkspaceInvitationStatus, AFWorkspaceSettings, GlobalComment,
|
||||
Reaction,
|
||||
InvitationCodeInfo, Reaction,
|
||||
};
|
||||
use futures_util::stream::BoxStream;
|
||||
use sqlx::{types::uuid, Executor, PgPool, Postgres, Transaction};
|
||||
|
@ -1530,6 +1530,48 @@ pub async fn select_invited_workspace_id(
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn select_invitation_code_info<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
invite_code: &str,
|
||||
uid: i64,
|
||||
) -> Result<Vec<InvitationCodeInfo>, AppError> {
|
||||
let info_list = sqlx::query_as!(
|
||||
InvitationCodeInfo,
|
||||
r#"
|
||||
WITH invited_workspace_member AS (
|
||||
SELECT
|
||||
invite_code,
|
||||
COUNT(*) AS member_count,
|
||||
COUNT(CASE WHEN uid = $2 THEN uid END) > 0 AS is_member
|
||||
FROM af_workspace_invite_code
|
||||
JOIN af_workspace_member USING (workspace_id)
|
||||
WHERE invite_code = $1
|
||||
AND (expires_at IS NULL OR expires_at < NOW())
|
||||
GROUP BY invite_code
|
||||
)
|
||||
SELECT
|
||||
workspace_id,
|
||||
owner_profile.name AS "owner_name!",
|
||||
owner_profile.metadata ->> 'icon_url' AS owner_avatar,
|
||||
af_workspace.workspace_name AS "workspace_name!",
|
||||
af_workspace.icon AS workspace_icon_url,
|
||||
invited_workspace_member.member_count AS "member_count!",
|
||||
invited_workspace_member.is_member AS "is_member!"
|
||||
FROM af_workspace_invite_code
|
||||
JOIN af_workspace USING (workspace_id)
|
||||
JOIN af_user AS owner_profile ON af_workspace.owner_uid = owner_profile.uid
|
||||
JOIN invited_workspace_member USING (invite_code)
|
||||
WHERE invite_code = $1
|
||||
"#,
|
||||
invite_code,
|
||||
uid
|
||||
)
|
||||
.fetch_all(executor)
|
||||
.await?;
|
||||
|
||||
Ok(info_list)
|
||||
}
|
||||
|
||||
pub async fn upsert_workspace_member_uid<'a, E: Executor<'a, Database = Postgres>>(
|
||||
executor: E,
|
||||
workspace_id: &Uuid,
|
||||
|
|
24
src/api/invite_code.rs
Normal file
24
src/api/invite_code.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use actix_web::{
|
||||
web::{self, Data, Json},
|
||||
Result, Scope,
|
||||
};
|
||||
use authentication::jwt::UserUuid;
|
||||
use database_entity::dto::{GetInvitationCodeInfoQuery, InvitationCodeInfo};
|
||||
use shared_entity::response::{AppResponse, JsonAppResponse};
|
||||
|
||||
use crate::{biz::workspace::invite::get_invitation_code_info, state::AppState};
|
||||
|
||||
pub fn invite_code_scope() -> Scope {
|
||||
web::scope("/api/invite-code-info")
|
||||
.service(web::resource("").route(web::get().to(get_invite_code_info_handler)))
|
||||
}
|
||||
|
||||
async fn get_invite_code_info_handler(
|
||||
user_uuid: UserUuid,
|
||||
query: web::Query<GetInvitationCodeInfoQuery>,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<InvitationCodeInfo>> {
|
||||
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
|
||||
let info = get_invitation_code_info(&state.pg_pool, &query.code, uid).await?;
|
||||
Ok(Json(AppResponse::Ok().with_data(info)))
|
||||
}
|
|
@ -3,6 +3,7 @@ pub mod ai;
|
|||
pub mod chat;
|
||||
pub mod data_import;
|
||||
pub mod file_storage;
|
||||
pub mod invite_code;
|
||||
pub mod metrics;
|
||||
pub mod search;
|
||||
pub mod server_info;
|
||||
|
|
|
@ -55,6 +55,7 @@ use crate::api::ai::ai_completion_scope;
|
|||
use crate::api::chat::chat_scope;
|
||||
use crate::api::data_import::data_import_scope;
|
||||
use crate::api::file_storage::file_storage_scope;
|
||||
use crate::api::invite_code::invite_code_scope;
|
||||
use crate::api::metrics::metrics_scope;
|
||||
use crate::api::search::search_scope;
|
||||
use crate::api::server_info::server_info_scope;
|
||||
|
@ -152,6 +153,7 @@ pub async fn run_actix_server(
|
|||
.service(server_info_scope())
|
||||
.service(user_scope())
|
||||
.service(workspace_scope())
|
||||
.service(invite_code_scope())
|
||||
.service(collab_scope())
|
||||
.service(ws_scope())
|
||||
.service(file_storage_scope())
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use app_error::AppError;
|
||||
use database::workspace::{
|
||||
insert_workspace_invite_code, select_invited_workspace_id, upsert_workspace_member_uid,
|
||||
insert_workspace_invite_code, select_invitation_code_info, select_invited_workspace_id,
|
||||
upsert_workspace_member_uid,
|
||||
};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use database_entity::dto::{AFRole, WorkspaceInviteToken};
|
||||
use database_entity::dto::{AFRole, InvitationCodeInfo, WorkspaceInviteToken};
|
||||
|
||||
const INVITE_LINK_CODE_LENGTH: usize = 16;
|
||||
|
||||
|
@ -40,3 +41,15 @@ pub async fn join_workspace_invite_by_code(
|
|||
upsert_workspace_member_uid(pg_pool, &invited_workspace_id, uid, AFRole::Member).await?;
|
||||
Ok(invited_workspace_id)
|
||||
}
|
||||
|
||||
pub async fn get_invitation_code_info(
|
||||
pg_pool: &PgPool,
|
||||
invitation_code: &str,
|
||||
uid: i64,
|
||||
) -> Result<InvitationCodeInfo, AppError> {
|
||||
let info_list = select_invitation_code_info(pg_pool, invitation_code, uid).await?;
|
||||
info_list
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or(AppError::InvalidInvitationCode)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,16 @@ async fn join_workspace_by_invite_code() {
|
|||
.await
|
||||
.unwrap()
|
||||
.code;
|
||||
let invitation_code_info = invitee_client
|
||||
.get_invitation_code_info(&invitation_code)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(invitation_code_info.is_member, Some(false));
|
||||
assert_eq!(invitation_code_info.member_count, 1);
|
||||
assert_eq!(
|
||||
invitation_code_info.workspace_name,
|
||||
workspaces[0].workspace_name
|
||||
);
|
||||
let invited_workspace_id = invitee_client
|
||||
.join_workspace_by_invitation_code(&invitation_code)
|
||||
.await
|
||||
|
|
Loading…
Add table
Reference in a new issue