Skip to content

Commit

Permalink
feat: role changed email
Browse files Browse the repository at this point in the history
  • Loading branch information
darkskygit committed Dec 20, 2024
1 parent cbaf35d commit adb9ac8
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 22 deletions.
6 changes: 6 additions & 0 deletions packages/backend/server/src/base/event/def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export interface WorkspaceEvents {
workspaceId: Workspace['id'];
}>;
requestApproved: Payload<{ inviteId: string }>;
roleChanged: Payload<{
userId: User['id'];
workspaceId: Workspace['id'];
permission: number;
}>;
ownerTransferred: Payload<{ email: string; workspaceId: Workspace['id'] }>;
updated: Payload<{ workspaceId: Workspace['id']; count: number }>;
};
deleted: Payload<Workspace['id']>;
Expand Down
25 changes: 24 additions & 1 deletion packages/backend/server/src/base/mailer/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { URLHelper } from '../helpers';
import { metrics } from '../metrics';
import type { MailerService, Options } from './mailer';
import { MAILER_SERVICE } from './mailer';
import { emailTemplate } from './template';
import {
emailTemplate,
getRoleChangedTemplate,
type RoleChangedMailParams,
} from './template';

@Injectable()
export class MailService {
constructor(
Expand Down Expand Up @@ -311,4 +316,22 @@ export class MailService {
});
return this.sendMail({ to, subject: title, html });
}

async sendRoleChangedEmail(to: string, ws: RoleChangedMailParams) {
const { subject, title, content } = getRoleChangedTemplate(ws);
const html = emailTemplate({ title, content });
console.log({ subject, title, content, to });
return this.sendMail({ to, subject, html });
}

async sendOwnerTransferred(to: string, ws: { name: string }) {
const { name: workspaceName } = ws;
const title = `Your ownership of ${workspaceName} has been transferred`;

const html = emailTemplate({
title: 'Ownership transferred',
content: `You have transferred ownership of ${workspaceName}. You are now a admin in this workspace.`,
});
return this.sendMail({ to, subject: title, html });
}
}
35 changes: 35 additions & 0 deletions packages/backend/server/src/base/mailer/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,38 @@ export const emailTemplate = ({
</table>
</body>`;
};

type RoleChangedMail = {
subject: string;
title: string;
content: string;
};

export type RoleChangedMailParams = {
name: string;
role: 'owner' | 'admin' | 'member' | 'readonly';
};

export const getRoleChangedTemplate = (
ws: RoleChangedMailParams
): RoleChangedMail => {
const { name, role } = ws;
let subject = `You are now an ${role} of ${name}`;
let title = 'Role update in workspace';
let content = `Your role in ${name} has been changed to ${role}. You can continue to collaborate in this workspace.`;

switch (role) {
case 'owner':
title = 'Welcome, new workspace owner!';
content = `You have been assigned as the owner of ${name}. As a workspace owner, you have full control over this team workspace.`;
break;
case 'admin':
title = `You've been promoted to admin.`;
content = `You have been promoted to admin of ${name}. As an admin, you can help the workspace owner manage members in this workspace.`;
break;
default:
subject = `Your role has been changed in ${name}`;
break;
}
return { subject, title, content };
};
65 changes: 45 additions & 20 deletions packages/backend/server/src/core/workspaces/resolvers/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Injectable, Logger } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { getStreamAsBuffer } from 'get-stream';

import { Cache, MailService } from '../../../base';
import { Cache, MailService, UserNotFound } from '../../../base';
import { DocContentService } from '../../doc-renderer';
import { PermissionService } from '../../permission';
import { Permission, PermissionService } from '../../permission';
import { WorkspaceBlobStorage } from '../../storage';
import { UserService } from '../../user';

Expand All @@ -17,6 +17,13 @@ export type InviteInfo = {
inviteeUserId?: string;
};

const PermissionToRole = {
[Permission.Read]: 'readonly' as const,
[Permission.Write]: 'member' as const,
[Permission.Admin]: 'admin' as const,
[Permission.Owner]: 'owner' as const,
};

@Injectable()
export class WorkspaceService {
private readonly logger = new Logger(WorkspaceService.name);
Expand Down Expand Up @@ -78,6 +85,27 @@ export class WorkspaceService {
};
}

private async getInviteeEmailTarget(inviteId: string) {
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
if (!inviteeUserId) {
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
return;
}
const workspace = await this.getWorkspaceInfo(workspaceId);
const invitee = await this.user.findUserById(inviteeUserId);
if (!invitee) {
this.logger.error(
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
);
return;
}

return {
email: invitee.email,
workspace,
};
}

async sendAcceptedEmail(inviteId: string) {
const { workspaceId, inviterUserId, inviteeUserId } =
await this.getInviteInfo(inviteId);
Expand Down Expand Up @@ -167,24 +195,21 @@ export class WorkspaceService {
await this.mailer.sendReviewDeclinedEmail(email, { name: workspaceName });
}

private async getInviteeEmailTarget(inviteId: string) {
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
if (!inviteeUserId) {
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
return;
}
const workspace = await this.getWorkspaceInfo(workspaceId);
const invitee = await this.user.findUserById(inviteeUserId);
if (!invitee) {
this.logger.error(
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
);
return;
}
async sendRoleChangedEmail(
userId: string,
ws: { id: string; role: Permission }
) {
const user = await this.user.findUserById(userId);
if (!user) throw new UserNotFound();
const workspace = await this.getWorkspaceInfo(ws.id);
await this.mailer.sendRoleChangedEmail(user?.email, {
name: workspace.name,
role: PermissionToRole[ws.role],
});
}

return {
email: invitee.email,
workspace,
};
async sendOwnerTransferred(email: string, ws: { id: string }) {
const workspace = await this.getWorkspaceInfo(ws.id);
await this.mailer.sendOwnerTransferred(email, { name: workspace.name });
}
}
38 changes: 37 additions & 1 deletion packages/backend/server/src/core/workspaces/resolvers/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RequestMutex,
TooManyRequest,
URLHelper,
UserFriendlyError,
} from '../../../base';
import { CurrentUser } from '../../auth';
import { Permission, PermissionService } from '../../permission';
Expand Down Expand Up @@ -311,7 +312,17 @@ export class TeamWorkspaceResolver {
);

if (result) {
// TODO(@darkskygit): send team role changed mail
this.event.emit('workspace.members.roleChanged', {
userId,
workspaceId,
permission,
});
if (permission === Permission.Owner) {
this.event.emit('workspace.members.ownerTransferred', {
email: user.email,
workspaceId,
});
}
}

return result;
Expand All @@ -320,6 +331,7 @@ export class TeamWorkspaceResolver {
}
} catch (e) {
this.logger.error('failed to invite user', e);
if (e instanceof UserFriendlyError) return e;
return new TooManyRequest();
}
}
Expand Down Expand Up @@ -353,4 +365,28 @@ export class TeamWorkspaceResolver {
// send approve mail
await this.workspaceService.sendReviewApproveEmail(inviteId);
}

@OnEvent('workspace.members.roleChanged')
async onRoleChanged({
userId,
workspaceId,
permission,
}: EventPayload<'workspace.members.roleChanged'>) {
// send role changed mail
await this.workspaceService.sendRoleChangedEmail(userId, {
id: workspaceId,
role: permission,
});
}

@OnEvent('workspace.members.ownerTransferred')
async onOwnerTransferred({
email,
workspaceId,
}: EventPayload<'workspace.members.ownerTransferred'>) {
// send role changed mail
await this.workspaceService.sendOwnerTransferred(email, {
id: workspaceId,
});
}
}

0 comments on commit adb9ac8

Please sign in to comment.