feat: invite to workspace email wait (#1057)
* feat: invite to workspace email wait * feat: add option to skip email * fix: docker compose ci mailer settings
This commit is contained in:
parent
22887430e8
commit
7cd7ea1f9e
|
|
@ -204,6 +204,8 @@ pub async fn invite_user_to_workspace(
|
|||
let invi = vec![WorkspaceMemberInvitation {
|
||||
email: user_email.to_string(),
|
||||
role: AFRole::Member,
|
||||
skip_email_send: true,
|
||||
..Default::default()
|
||||
}];
|
||||
|
||||
let http_client = reqwest::Client::new();
|
||||
|
|
|
|||
|
|
@ -134,6 +134,11 @@ services:
|
|||
- APPFLOWY_AI_SERVER_HOST=${APPFLOWY_AI_SERVER_HOST}
|
||||
- APPFLOWY_AI_SERVER_PORT=${APPFLOWY_AI_SERVER_PORT}
|
||||
- APPFLOWY_WEB_URL=${APPFLOWY_WEB_URL}
|
||||
- APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST}
|
||||
- APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT}
|
||||
- APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME}
|
||||
- APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL}
|
||||
- APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
|
|
|||
|
|
@ -368,7 +368,12 @@ impl TestClient {
|
|||
.api_client
|
||||
.invite_workspace_members(
|
||||
workspace_id,
|
||||
vec![WorkspaceMemberInvitation { email, role }],
|
||||
vec![WorkspaceMemberInvitation {
|
||||
email,
|
||||
role,
|
||||
skip_email_send: true,
|
||||
..Default::default()
|
||||
}],
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ pub async fn insert_workspace_invitation(
|
|||
workspace_id: &uuid::Uuid,
|
||||
inviter_uuid: &Uuid,
|
||||
invitee_email: &str,
|
||||
invitee_role: AFRole,
|
||||
invitee_role: &AFRole,
|
||||
) -> Result<(), AppError> {
|
||||
let role_id: i32 = invitee_role.into();
|
||||
sqlx::query!(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,22 @@ pub struct CreateWorkspaceMember {
|
|||
pub struct WorkspaceMemberInvitation {
|
||||
pub email: String,
|
||||
pub role: AFRole,
|
||||
|
||||
#[serde(default)]
|
||||
pub skip_email_send: bool,
|
||||
#[serde(default)]
|
||||
pub wait_email_send: bool,
|
||||
}
|
||||
|
||||
impl Default for WorkspaceMemberInvitation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
email: "".to_string(),
|
||||
role: AFRole::Member,
|
||||
skip_email_send: false,
|
||||
wait_email_send: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ async fn post_workspace_invite_handler(
|
|||
.enforce_role(&uid, &workspace_id.to_string(), AFRole::Owner)
|
||||
.await?;
|
||||
|
||||
let invited_members = payload.into_inner();
|
||||
let invitations = payload.into_inner();
|
||||
workspace::ops::invite_workspace_members(
|
||||
&state.mailer,
|
||||
&state.gotrue_admin,
|
||||
|
|
@ -396,7 +396,7 @@ async fn post_workspace_invite_handler(
|
|||
&state.gotrue_client,
|
||||
&user_uuid,
|
||||
&workspace_id,
|
||||
invited_members,
|
||||
invitations,
|
||||
state.config.appflowy_web_url.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ use aws_sdk_s3::types::{
|
|||
BucketInfo, BucketLocationConstraint, BucketType, CreateBucketConfiguration,
|
||||
};
|
||||
use collab::lock::Mutex;
|
||||
use mailer::config::MailerSetting;
|
||||
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
|
||||
use openssl::x509::X509;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
|
|
@ -316,7 +317,7 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
|
|||
.connect_lazy();
|
||||
|
||||
let grpc_history_client = Arc::new(Mutex::new(HistoryClient::new(channel)));
|
||||
let mailer = get_mailer(config).await?;
|
||||
let mailer = get_mailer(&config.mailer).await?;
|
||||
|
||||
info!("Application state initialized");
|
||||
Ok(AppState {
|
||||
|
|
@ -446,13 +447,14 @@ async fn create_bucket_if_not_exists(
|
|||
}
|
||||
}
|
||||
|
||||
async fn get_mailer(config: &Config) -> Result<AFCloudMailer, Error> {
|
||||
async fn get_mailer(mailer: &MailerSetting) -> Result<AFCloudMailer, Error> {
|
||||
info!("Connecting to mailer with setting: {:?}", mailer);
|
||||
let mailer = Mailer::new(
|
||||
config.mailer.smtp_username.clone(),
|
||||
config.mailer.smtp_email.clone(),
|
||||
config.mailer.smtp_password.clone(),
|
||||
&config.mailer.smtp_host,
|
||||
config.mailer.smtp_port,
|
||||
mailer.smtp_username.clone(),
|
||||
mailer.smtp_email.clone(),
|
||||
mailer.smtp_password.clone(),
|
||||
&mailer.smtp_host,
|
||||
mailer.smtp_port,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ pub async fn invite_workspace_members(
|
|||
workspace_id,
|
||||
inviter,
|
||||
invitation.email.as_str(),
|
||||
invitation.role,
|
||||
&invitation.role,
|
||||
)
|
||||
.await?;
|
||||
invite_id
|
||||
|
|
@ -457,26 +457,32 @@ pub async fn invite_workspace_members(
|
|||
}
|
||||
};
|
||||
|
||||
// send email can be slow, so send email in background
|
||||
let cloned_mailer = mailer.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = cloned_mailer
|
||||
.send_workspace_invite(
|
||||
&invitation.email,
|
||||
WorkspaceInviteMailerParam {
|
||||
user_icon_url,
|
||||
username: inviter_name,
|
||||
workspace_name,
|
||||
workspace_icon_url,
|
||||
workspace_member_count,
|
||||
accept_url,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to send workspace invite email: {:?}", err);
|
||||
};
|
||||
});
|
||||
if !invitation.skip_email_send {
|
||||
let cloned_mailer = mailer.clone();
|
||||
let email_sending = tokio::spawn(async move {
|
||||
cloned_mailer
|
||||
.send_workspace_invite(
|
||||
&invitation.email,
|
||||
WorkspaceInviteMailerParam {
|
||||
user_icon_url,
|
||||
username: inviter_name,
|
||||
workspace_name,
|
||||
workspace_icon_url,
|
||||
workspace_member_count,
|
||||
accept_url,
|
||||
},
|
||||
)
|
||||
.await
|
||||
});
|
||||
if invitation.wait_email_send {
|
||||
email_sending.await??;
|
||||
}
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Skipping email send for workspace invite to {}",
|
||||
invitation.email
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
txn
|
||||
|
|
|
|||
|
|
@ -33,6 +33,15 @@ impl AFCloudMailer {
|
|||
&subject,
|
||||
)
|
||||
.await
|
||||
.map(|_| tracing::info!("Sent workspace invite email to {}", email))
|
||||
.map_err(|err| {
|
||||
tracing::error!(
|
||||
"Failed to send workspace invite email to {}: {}",
|
||||
email,
|
||||
err
|
||||
);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_workspace_access_request(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::Context;
|
||||
use app_error::ErrorCode;
|
||||
use client_api_test::generate_unique_registered_user_client;
|
||||
use client_api_test::{generate_unique_registered_user_client, TestClient};
|
||||
use database_entity::dto::{AFRole, AFWorkspaceInvitationStatus};
|
||||
use shared_entity::dto::workspace_dto::{QueryWorkspaceParam, WorkspaceMemberInvitation};
|
||||
|
||||
|
|
@ -30,6 +31,8 @@ async fn invite_workspace_crud() {
|
|||
vec![WorkspaceMemberInvitation {
|
||||
email: bob.email.clone(),
|
||||
role: AFRole::Member,
|
||||
skip_email_send: true,
|
||||
..Default::default()
|
||||
}],
|
||||
)
|
||||
.await
|
||||
|
|
@ -149,3 +152,25 @@ async fn invite_workspace_crud() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invite_wait_email_sending_success() {
|
||||
let c1 = TestClient::new_user_without_ws_conn().await;
|
||||
let c2 = TestClient::new_user_without_ws_conn().await;
|
||||
|
||||
let workspace_id = c1.workspace_id().await;
|
||||
let _: () = c1
|
||||
.api_client
|
||||
.invite_workspace_members(
|
||||
&workspace_id,
|
||||
vec![WorkspaceMemberInvitation {
|
||||
email: c2.user.email,
|
||||
role: AFRole::Member,
|
||||
skip_email_send: false,
|
||||
wait_email_send: true,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.context("failed to send email to invite workspace members")
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ async fn add_duplicate_workspace_members() {
|
|||
vec![WorkspaceMemberInvitation {
|
||||
email: c2.email().await,
|
||||
role: AFRole::Member,
|
||||
skip_email_send: true,
|
||||
..Default::default()
|
||||
}],
|
||||
)
|
||||
.await
|
||||
|
|
@ -131,6 +133,8 @@ async fn add_not_exist_workspace_members() {
|
|||
vec![WorkspaceMemberInvitation {
|
||||
email: email.clone(),
|
||||
role: AFRole::Member,
|
||||
skip_email_send: true,
|
||||
..Default::default()
|
||||
}],
|
||||
)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ async fn invite_user_to_workspace(
|
|||
vec![WorkspaceMemberInvitation {
|
||||
email: member_email.to_string(),
|
||||
role: AFRole::Member,
|
||||
skip_email_send: true,
|
||||
..Default::default()
|
||||
}],
|
||||
)
|
||||
.await
|
||||
|
|
|
|||
Loading…
Reference in New Issue