Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: send notification when access request is approved #854

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf-8">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<style>
td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
</style>
<![endif]-->
<title>Your access request has been approved</title>
<style>
.hover-opacity-90:hover {
opacity: 0.9 !important
}
@media (max-width: 600px) {
.sm-px-4 {
padding-left: 16px !important;
padding-right: 16px !important
}
.sm-py-12 {
padding-top: 48px !important;
padding-bottom: 48px !important
}
}
</style>
</head>
<body style="margin: 0; width: 100%; background-color: #faf5ff; padding: 0; -webkit-font-smoothing: antialiased; word-break: break-word">
<div style="display: none">
Workspace access request approved notification
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
</div>
<div role="article" aria-roledescription="email" aria-label="Your access request has been approved" lang="en">
<div class="sm-px-4 sm-py-12" style="background-color: #faf5ff; padding: 96px 48px; font-family: Helvetica, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif; color: #000">
<table align="center" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<p style="width: 100%; white-space: normal; overflow-wrap: break-word; text-align: center; font-size: 24px">
<span>Your request to access </span>
<span style="font-size: 30px; font-weight: 700">{{ workspace_name }}</span>
<span> has been approved </span>
</p>
<div role="separator" style="background-color: #cbd5e1; height: 1px; line-height: 1px; margin: 24px 20%">&zwj;</div>
<table align="center" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 60px">
<div style="margin-right: 8px; height: 60px; width: 60px; overflow: hidden; border-radius: 16px; background-color: #fff; border: 2px solid black">
<img src="{{ workspace_icon_url }}" width="100%" height="100%" alt="{{ workspace_name }}" style="max-width: 100%; vertical-align: middle; line-height: 1; overflow: hidden; object-fit: cover">
</div>
</td>
<td>
<div style="margin-bottom: 8px; font-weight: 700">{{ workspace_name }}</div>
<div style="font-size: 14px; color: #64748b">
{{ workspace_member_count }} members
</div>
</td>
</tr>
</table>
<div style="text-align: center;">
<a href="{{ launch_workspace_url }}" class="hover-opacity-90" style="margin-top: 32px; margin-bottom: 32px; display: inline-block; width: 60%; cursor: pointer; border-radius: 16px; padding: 16px 24px; color: #f8fafc; text-decoration: none; background-color: #9327ff; font-size: 20px; font-weight: 400; line-height: 20px">
<!--[if mso]>
<i style="mso-font-width: 150%; mso-text-raise: 30px" hidden>&emsp;</i>
<![endif]-->
<span style="mso-text-raise: 16px">
<div style="font-size: 24px; font-weight: 500">View workspace</div>
</span>
<!--[if mso]>
<i hidden style="mso-font-width: 150%;">&emsp;&#8203;</i>
<![endif]-->
</a>
</div>
<div style="
margin-left: auto;
margin-right: auto;
width: 70%;
text-align: center;
font-size: 14px;
line-height: 18px;
color: #64748b;
">
By clicking "View workspace" above, you confirm that you have read,
understood, and agreed to AppFlowy's
<a href="https://appflowy.io/terms/app" style="color: #64748b">Terms & Conditions</a>
and
<a href="https://appflowy.io/privacy/app" style="color: #64748b">Privacy Policy</a>.
</div>
<div role="separator" style="background-color: #cbd5e1; height: 1px; line-height: 1px; margin: 24px 20%;">&zwj;</div>
</td>
</tr>
<tr>
<td style="padding-left: 24px; padding-right: 24px; text-align: center; font-size: 12px; color: #475569">
<p style="margin: 0 0 16px; cursor: pointer; text-transform: uppercase">
<a href="https://appflowy.io">
<img src="https://raw.githubusercontent.com/AppFlowy-IO/AppFlowy-Cloud/main/assets/mailer_templates/build_production/images/appflowy-logo.png" width="150px" style="max-width: 100%; vertical-align: middle; line-height: 1;" alt="">
</a>
</p>
<p style="margin: 0; font-size: 14px; font-weight: 500; color: #000;">
Bring projects, knowledge, and teams together with the power of AI.
</p>
<p style="cursor: default">
<a href="https://twitter.com/appflowy" style="margin-right: 16px; color: #4338ca; text-decoration: none">
<img src="https://raw.githubusercontent.com/AppFlowy-IO/AppFlowy-Cloud/main/assets/mailer_templates/build_production/images/twitter.png" width="20" alt="Maizzle" style="max-width: 100%; vertical-align: middle; line-height: 1;">
</a>
<a href="https://www.reddit.com/r/AppFlowy" style="margin-right: 16px; color: #4338ca; text-decoration: none;">
<img src="https://raw.githubusercontent.com/AppFlowy-IO/AppFlowy-Cloud/main/assets/mailer_templates/build_production/images/reddit.png" width="20" alt="Maizzle" style="max-width: 100%; vertical-align: middle; line-height: 1;">
</a>
<a href="https://github.com/AppFlowy-IO/AppFlowy" style="margin-right: 16px; color: #4338ca; text-decoration: none;">
<img src="https://raw.githubusercontent.com/AppFlowy-IO/AppFlowy-Cloud/main/assets/mailer_templates/build_production/images/github.png" width="20" alt="Maizzle" style="max-width: 100%; vertical-align: middle; line-height: 1;">
</a>
<a href="https://discord.gg/9Q2xaN37tV" style="margin-right: 16px; color: #4338ca; text-decoration: none;">
<img src="https://raw.githubusercontent.com/AppFlowy-IO/AppFlowy-Cloud/main/assets/mailer_templates/build_production/images/discord.png" width="20" alt="Maizzle" style="max-width: 100%; vertical-align: middle; line-height: 1;">
</a>
</p>
</td>
</tr>
</table>
</div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions email_template/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = {
userName: "John Doe",
acceptUrl: "https://appflowy.io",
approveUrl: "https://appflowy.io",
launchWorkspaceUrl: "https://appflowy.io",
workspaceName: "AppFlowy",
workspaceMembersCount: "100",
workspaceIconURL: "https://cdn-icons-png.flaticon.com/512/1078/1078013.png",
Expand Down
1 change: 1 addition & 0 deletions email_template/config.production.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
userName: "{{ username }}",
acceptUrl: "{{ accept_url }}",
approveUrl: "{{ approve_url }}",
launchWorkspaceUrl: "{{ launch_workspace_url }}",
workspaceName: "{{ workspace_name }}",
workspaceMembersCount: "{{ workspace_member_count }}",
workspaceIconURL: "{{ workspace_icon_url }}",
Expand Down
135 changes: 135 additions & 0 deletions email_template/src/templates/access_request_approved_notification.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
title: "Your access request has been approved"
preheader: "Workspace access request approved notification"
bodyClass: bg-purple-50
---

<x-main>
<div
class="bg-purple-50 font-helvetica sm:px-4 px-12 sm:py-12 py-24 text-black"
>
<table align="center">
<tr>
<td class="w-[552px] max-w-full">
<p class="w-full text-center break-words whitespace-normal text-2xl">
<span class="mx-2=1">Your request to access </span>
<span class="text-3xl font-bold">{{ workspaceName }}</span>
<span class="mx-2=1"> has been approved </span>
</p>
<x-divider space-x="20%" />
<table align="center">
<tr>
<td class="w-[60px]">
<div
style="border: 2px solid black"
class="rounded-2xl mr-2 w-[60px] h-[60px] bg-white overflow-hidden"
>
<img
src="{{ workspaceIconURL }}"
class="overflow-hidden object-cover"
width="100%"
height="100%"
alt="{{ workspaceName }}"
/>
</div>
</td>
<td>
<div class="font-bold mb-2">{{ workspaceName }}</div>
<div class="text-sm text-slate-500">
{{ workspaceMembersCount }} members
</div>
</td>
</tr>
</table>
<x-button
align="center"
class="hover:opacity-90 cursor-pointer !text-xl !leading-[20px] !bg-[#9327ff] !font-normal w-[60%] my-8 rounded-2xl"
href="{{ launchWorkspaceUrl }}"
>
<div class="font-medium text-[24px]">View workspace</div>
</x-button>
<div
style="
margin-left: auto;
margin-right: auto;
width: 70%;
text-align: center;
font-size: 14px;
line-height: 18px;
color: #64748b;
"
>
By clicking "View workspace" above, you confirm that you have read,
understood, and agreed to AppFlowy's
<a href="https://appflowy.io/terms/app" style="color: #64748b"
>Terms & Conditions</a
>
and
<a href="https://appflowy.io/privacy/app" style="color: #64748b"
>Privacy Policy</a
>.
</div>
<x-divider space-x="20%" />
</td>
</tr>
<tr>
<td class="text-center text-slate-600 text-xs px-6">
<p class="m-0 mb-4 uppercase cursor-pointer">
<a href="https://appflowy.io">
<img
src="{{ cdnBaseUrl }}images/appflowy-logo.png"
width="150px"
/>
</a>
</p>
<p class="m-0 text-sm text-black font-medium">
Bring projects, knowledge, and teams together with the power of AI.
</p>

<p class="cursor-default">
<a
href="https://twitter.com/appflowy"
class="text-indigo-700 [text-decoration:none] mr-4"
>
<img
src="{{ cdnBaseUrl }}images/twitter.png"
width="20"
alt="Maizzle"
/>
</a>
<a
href="https://www.reddit.com/r/AppFlowy"
class="text-indigo-700 [text-decoration:none] mr-4"
>
<img
src="{{ cdnBaseUrl }}images/reddit.png"
width="20"
alt="Maizzle"
/>
</a>
<a
href="https://github.com/AppFlowy-IO/AppFlowy"
class="text-indigo-700 [text-decoration:none] mr-4"
>
<img
src="{{ cdnBaseUrl }}images/github.png"
width="20"
alt="Maizzle"
/>
</a>
<a
href="https://discord.gg/9Q2xaN37tV"
class="text-indigo-700 [text-decoration:none] mr-4"
>
<img
src="{{ cdnBaseUrl }}images/discord.png"
width="20"
alt="Maizzle"
/>
</a>
</p>
</td>
</tr>
</table>
</div>
</x-main>
19 changes: 17 additions & 2 deletions src/api/access_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,22 @@ async fn post_approve_access_request_handler(
let uid = state.user_cache.get_user_uid(&uuid).await?;
let access_request_id = access_request_id.into_inner();
let is_approved = approve_access_request_params.is_approved;
approve_or_reject_access_request(&state.pg_pool, access_request_id, uid, *uuid, is_approved)
.await?;
let appflowy_web_url = state
.config
.appflowy_web_url
.clone()
.ok_or(AppError::Internal(anyhow!(
"AppFlowy web url has not been set"
)))?;
approve_or_reject_access_request(
&state.pg_pool,
state.mailer.clone(),
&appflowy_web_url,
access_request_id,
uid,
*uuid,
is_approved,
)
.await?;
Ok(Json(AppResponse::Ok()))
}
35 changes: 33 additions & 2 deletions src/biz/access_request/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
folder_view::{to_dto_view_icon, to_view_layout},
ops::get_latest_collab_folder,
},
mailer::{Mailer, WorkspaceAccessRequestMailerParam},
mailer::{Mailer, WorkspaceAccessRequestApprovedMailerParam, WorkspaceAccessRequestMailerParam},
};

pub async fn create_access_request(
Expand Down Expand Up @@ -58,7 +58,7 @@ pub async fn create_access_request(
workspace_name: access_request.workspace.workspace_name,
workspace_icon_url,
workspace_member_count: access_request.workspace.member_count.unwrap_or(0),
approve_url: approve_url.to_string(),
approve_url,
},
)
.await
Expand Down Expand Up @@ -110,6 +110,8 @@ pub async fn get_access_request(

pub async fn approve_or_reject_access_request(
pg_pool: &PgPool,
mailer: Mailer,
appflowy_web_url: &str,
request_id: Uuid,
uid: i64,
user_uuid: Uuid,
Expand All @@ -133,6 +135,35 @@ pub async fn approve_or_reject_access_request(
role,
)
.await?;
let cloned_mailer = mailer.clone();
let launch_workspace_url = format!(
"{}/app/{}",
appflowy_web_url, &access_request.workspace.workspace_id
);

// use default icon until we have workspace icon
let workspace_icon_url =
"https://miro.medium.com/v2/resize:fit:2400/1*mTPfm7CwU31-tLhtLNkyJw.png".to_string();
tokio::spawn(async move {
if let Err(err) = cloned_mailer
.send_workspace_access_request_approval_notification(
&access_request.requester.name,
&access_request.requester.email,
WorkspaceAccessRequestApprovedMailerParam {
workspace_name: access_request.workspace.workspace_name,
workspace_icon_url,
workspace_member_count: access_request.workspace.member_count.unwrap_or(0),
launch_workspace_url,
},
)
.await
{
tracing::error!(
"Failed to send access request approved notification email: {:?}",
err
);
};
});
}
let status = if is_approved {
AFAccessRequestStatusColumn::Approved
Expand Down
Loading
Loading