Skip to content

Commit

Permalink
feat: more template
Browse files Browse the repository at this point in the history
  • Loading branch information
darkskygit committed Jan 7, 2025
1 parent a1c7ee8 commit 2d6018a
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 152 deletions.
6 changes: 3 additions & 3 deletions packages/backend/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"scripts": {
"build": "tsc",
"dev": "nodemon ./src/index.ts",
"dev:mail": "email dev -d src/base/mailer/templates",
"dev:mail": "email dev -d src/mail-templates",
"test": "ava --concurrency 1 --serial",
"test:copilot": "ava \"tests/**/copilot-*.spec.ts\"",
"test:coverage": "c8 ava --concurrency 1 --serial",
Expand Down Expand Up @@ -82,8 +82,6 @@
"openai": "^4.76.2",
"piscina": "^5.0.0-alpha.0",
"prisma": "^5.22.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"ses": "^1.10.0",
Expand Down Expand Up @@ -117,6 +115,8 @@
"c8": "^10.1.3",
"cross-env": "^7.0.3",
"nodemon": "^3.1.7",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-email": "3.0.4",
"sinon": "^19.0.2",
"supertest": "^7.0.0"
Expand Down
204 changes: 81 additions & 123 deletions packages/backend/server/src/base/mailer/mail.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { Inject, Injectable, Optional } from '@nestjs/common';

import {
renderChangeEmailMail,
renderChangeEmailNotificationMail,
renderChangePasswordMail,
renderMemberAcceptedMail,
renderMemberInviteMail,
renderMemberLeaveMail,
renderSetPasswordMail,
renderSignInMail,
renderSignUpMail,
renderVerifyChangeEmailMail,
renderVerifyEmailMail,
} from '../../mail-templates';
import { Config } from '../config';
import { MailerServiceIsNotConfigured } from '../error';
import { URLHelper } from '../helpers';
Expand All @@ -11,11 +24,6 @@ import {
getRoleChangedTemplate,
type RoleChangedMailParams,
} from './template';
import {
renderInviteEmail,
renderSignInEmail,
renderSignUpEmail,
} from './templates';

@Injectable()
export class MailService {
Expand Down Expand Up @@ -50,192 +58,142 @@ export class MailService {
return !!this.mailer;
}

async sendInviteEmail(
to: string,
inviteId: string,
invitationInfo: {
workspace: { id: string; name: string; avatar: string };
user: { avatar: string; name: string };
}
) {
const {
user: { name: userName, avatar: userAvatar },
workspace: { name: workspaceName, avatar: workspaceAvatar },
} = invitationInfo;
const buttonUrl = this.url.link(`/invite/${inviteId}`);

const html = emailTemplate({
title: 'You are invited!',
content: await renderInviteEmail({
userName,
userAvatar,
workspaceName,
url: buttonUrl,
}),
buttonContent: 'Accept & Join',
buttonUrl,
});
async sendSignUpMail(to: string, url: string) {
const { html, subject } = await renderSignUpMail({ url });

return this.sendMail({
to,
subject: `${userName} invited you to join ${workspaceName}`,
subject,
html,
attachments: [
{
cid: 'workspaceAvatar',
filename: 'image.png',
content: workspaceAvatar,
encoding: 'base64',
},
],
});
}

async sendSignUpMail(to: string, url: string) {
return this.sendMail({
to,
html: await renderSignUpEmail({ url }),
subject: 'Your AFFiNE account is waiting for you!',
});
}

async sendSignInMail(to: string, url: string) {
const { html, subject } = await renderSignInMail({ url });

return this.sendMail({
to,
html: await renderSignInEmail({ url }),
subject: 'Sign in to AFFiNE',
subject,
html,
});
}

async sendChangePasswordEmail(to: string, url: string) {
const html = emailTemplate({
title: 'Modify your AFFiNE password',
content:
'Click the button below to reset your password. The magic link will expire in 30 minutes.',
buttonContent: 'Set new password',
buttonUrl: url,
});
async sendChangePasswordMail(to: string, url: string) {
const { html, subject } = await renderChangePasswordMail({ url });

return this.sendMail({
to,
subject: `Modify your AFFiNE password`,
subject,
html,
});
}

async sendSetPasswordEmail(to: string, url: string) {
const html = emailTemplate({
title: 'Set your AFFiNE password',
content:
'Click the button below to set your password. The magic link will expire in 30 minutes.',
buttonContent: 'Set your password',
buttonUrl: url,
});
async sendSetPasswordMail(to: string, url: string) {
const { html, subject } = await renderSetPasswordMail({ url });

return this.sendMail({
to,
subject: `Set your AFFiNE password`,
subject,
html,
});
}

async sendChangeEmail(to: string, url: string) {
const html = emailTemplate({
title: 'Verify your current email for AFFiNE',
content:
'You recently requested to change the email address associated with your AFFiNE account. To complete this process, please click on the verification link below. This magic link will expire in 30 minutes.',
buttonContent: 'Verify and set up a new email address',
buttonUrl: url,
});
async sendChangeEmailMail(to: string, url: string) {
const { html, subject } = await renderChangeEmailMail({ url });

return this.sendMail({
to,
subject: `Verify your current email for AFFiNE`,
subject,
html,
});
}

async sendVerifyChangeEmail(to: string, url: string) {
const html = emailTemplate({
title: 'Verify your new email address',
content:
'You recently requested to change the email address associated with your AFFiNE account. To complete this process, please click on the verification link below. This magic link will expire in 30 minutes.',
buttonContent: 'Verify your new email address',
buttonUrl: url,
});
const { html, subject } = await renderVerifyChangeEmailMail({ url });

return this.sendMail({
to,
subject: `Verify your new email for AFFiNE`,
subject,
html,
});
}

async sendVerifyEmail(to: string, url: string) {
const html = emailTemplate({
title: 'Verify your email address',
content:
'You recently requested to verify the email address associated with your AFFiNE account. To complete this process, please click on the verification link below. This magic link will expire in 30 minutes.',
buttonContent: 'Verify your email address',
buttonUrl: url,
});
const { html, subject } = await renderVerifyEmailMail({ url });

return this.sendMail({
to,
subject: `Verify your email for AFFiNE`,
subject,
html,
});
}

async sendNotificationChangeEmail(to: string) {
const html = emailTemplate({
title: 'Email change successful',
content: `As per your request, we have changed your email. Please make sure you're using ${to} when you log in the next time. `,
});
const { html, subject } = await renderChangeEmailNotificationMail({ to });

return this.sendMail({
to,
subject: `Your email has been changed`,
subject,
html,
});
}

async sendAcceptedEmail(
async sendMemberInviteMail(
to: string,
{
inviteeName,
workspaceName,
}: {
inviteeName: string;
workspaceName: string;
inviteId: string,
invitationInfo: {
workspace: { id: string; name: string; avatar: string };
user: { avatar: string; name: string };
}
) {
const title = `${inviteeName} accepted your invitation`;

const html = emailTemplate({
title,
content: `${inviteeName} has joined ${workspaceName}`,
const {
user: { name: userName, avatar: userAvatar },
workspace: { name: workspaceName, avatar: workspaceAvatar },
} = invitationInfo;
const buttonUrl = this.url.link(`/invite/${inviteId}`);
const { html, subject } = await renderMemberInviteMail({
userName,
userAvatar,
workspaceName,
url: buttonUrl,
});
return this.sendMail({
to,
subject: title,
subject,
html,
attachments: [
{
cid: 'workspaceAvatar',
filename: 'image.png',
content: workspaceAvatar,
encoding: 'base64',
},
],
});
}

async sendLeaveWorkspaceEmail(
async sendMemberAcceptedEmail(
to: string,
{
inviteeName,
workspaceName,
}: {
inviteeName: string;
workspaceName: string;
}
props: { inviteeName: string; workspaceName: string }
) {
const title = `${inviteeName} left ${workspaceName}`;
const { html, subject } = await renderMemberAcceptedMail(props);

const html = emailTemplate({
title,
content: `${inviteeName} has left your workspace`,
return this.sendMail({
to,
subject,
html,
});
}

async sendMemberLeaveEmail(
to: string,
props: { inviteeName: string; workspaceName: string }
) {
const { html, subject } = await renderMemberLeaveMail(props);

return this.sendMail({
to,
subject: title,
subject,
html,
});
}
Expand Down
17 changes: 0 additions & 17 deletions packages/backend/server/src/base/mailer/templates/index.tsx

This file was deleted.

6 changes: 3 additions & 3 deletions packages/backend/server/src/core/auth/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,13 +398,13 @@ export class AuthService implements OnApplicationBootstrap {
}

async sendChangePasswordEmail(email: string, callbackUrl: string) {
return this.mailer.sendChangePasswordEmail(email, callbackUrl);
return this.mailer.sendChangePasswordMail(email, callbackUrl);
}
async sendSetPasswordEmail(email: string, callbackUrl: string) {
return this.mailer.sendSetPasswordEmail(email, callbackUrl);
return this.mailer.sendSetPasswordMail(email, callbackUrl);
}
async sendChangeEmail(email: string, callbackUrl: string) {
return this.mailer.sendChangeEmail(email, callbackUrl);
return this.mailer.sendChangeEmailMail(email, callbackUrl);
}
async sendVerifyChangeEmail(email: string, callbackUrl: string) {
return this.mailer.sendVerifyChangeEmail(email, callbackUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class WorkspaceService {
return false;
}

await this.mailer.sendAcceptedEmail(inviter.email, {
await this.mailer.sendMemberAcceptedEmail(inviter.email, {
inviteeName: invitee.name,
workspaceName: workspace.name,
});
Expand Down Expand Up @@ -191,7 +191,7 @@ export class WorkspaceService {

const owner = await this.permission.getWorkspaceOwner(target.workspace.id);

await this.mailer.sendInviteEmail(target.email, inviteId, {
await this.mailer.sendMemberInviteMail(target.email, inviteId, {
workspace: target.workspace,
user: {
avatar: owner.avatarUrl || '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ export class WorkspaceResolver {
const owner = await this.permissions.getWorkspaceOwner(workspaceId);

if (sendLeaveMail) {
await this.mailer.sendLeaveWorkspaceEmail(owner.email, {
await this.mailer.sendMemberLeaveEmail(owner.email, {
workspaceName,
inviteeName: user.name,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { EmailTemplate } from './components';

export type ChangeEmailNotificationProps = {
to: string;
};

export default function ChangeEmailNotification(
props: ChangeEmailNotificationProps
) {
return (
<EmailTemplate
title="Verify your current email for AFFiNE"
content={`As per your request, we have changed your email. Please make sure you're using ${props.to} when you log in the next time.`}
/>
);
}
Loading

0 comments on commit 2d6018a

Please sign in to comment.