feat(server): send mention email (#10859)

close CLOUD-170
This commit is contained in:
fengmk2
2025-03-20 09:26:42 +00:00
parent 5ea65b1709
commit 42745f059d
13 changed files with 323 additions and 7 deletions

View File

@@ -18,6 +18,7 @@ import {
} from '../../../models';
import { DocReader } from '../../doc';
import { NotificationService } from '../service';
interface Context {
module: TestingModule;
notificationService: NotificationService;
@@ -337,3 +338,46 @@ test('should raw doc title in mention notification if no doc found', async t =>
t.is(body2.doc.title, 'doc-title-1');
t.is(body2.doc.mode, DocMode.page);
});
test('should send mention email by user setting', async t => {
const { notificationService } = t.context;
const docId = randomUUID();
const notification = await notificationService.createMention({
userId: member.id,
body: {
workspaceId: workspace.id,
createdByUserId: owner.id,
doc: {
id: docId,
title: 'doc-title-1',
blockId: 'block-id-1',
mode: DocMode.page,
},
},
});
t.truthy(notification);
// should send mention email
const mentionMail = t.context.module.mails.last('Mention');
t.is(mentionMail.to, member.email);
// update user setting to not receive mention email
const mentionMailCount = t.context.module.mails.count('Mention');
await t.context.models.settings.set(member.id, {
receiveMentionEmail: false,
});
await notificationService.createMention({
userId: member.id,
body: {
workspaceId: workspace.id,
createdByUserId: owner.id,
doc: {
id: docId,
title: 'doc-title-2',
blockId: 'block-id-2',
mode: DocMode.page,
},
},
});
// should not send mention email
t.is(t.context.module.mails.count('Mention'), mentionMailCount);
});

View File

@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { DocStorageModule } from '../doc';
import { MailModule } from '../mail';
import { PermissionModule } from '../permission';
import { StorageModule } from '../storage';
import { NotificationJob } from './job';
@@ -8,7 +9,7 @@ import { NotificationResolver, UserNotificationResolver } from './resolver';
import { NotificationService } from './service';
@Module({
imports: [PermissionModule, DocStorageModule, StorageModule],
imports: [PermissionModule, DocStorageModule, StorageModule, MailModule],
providers: [
UserNotificationResolver,
NotificationResolver,

View File

@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
import { NotificationNotFound, PaginationInput } from '../../base';
import { NotificationNotFound, PaginationInput, URLHelper } from '../../base';
import {
InvitationNotificationCreate,
MentionNotification,
@@ -11,7 +11,9 @@ import {
UnionNotificationBody,
} from '../../models';
import { DocReader } from '../doc';
import { Mailer } from '../mail';
import { WorkspaceBlobStorage } from '../storage';
import { generateDocPath } from '../utils/doc';
@Injectable()
export class NotificationService {
@@ -20,7 +22,9 @@ export class NotificationService {
constructor(
private readonly models: Models,
private readonly docReader: DocReader,
private readonly workspaceBlobStorage: WorkspaceBlobStorage
private readonly workspaceBlobStorage: WorkspaceBlobStorage,
private readonly mailer: Mailer,
private readonly url: URLHelper
) {}
async cleanExpiredNotifications() {
@@ -28,7 +32,48 @@ export class NotificationService {
}
async createMention(input: MentionNotificationCreate) {
return await this.models.notification.createMention(input);
const notification = await this.models.notification.createMention(input);
await this.sendMentionEmail(input);
return notification;
}
private async sendMentionEmail(input: MentionNotificationCreate) {
const userSetting = await this.models.settings.get(input.userId);
if (!userSetting.receiveMentionEmail) {
return;
}
const receiver = await this.models.user.getWorkspaceUser(input.userId);
if (!receiver) {
return;
}
const doc = await this.models.doc.getMeta(
input.body.workspaceId,
input.body.doc.id
);
const title = doc?.title ?? input.body.doc.title;
const url = this.url.link(
generateDocPath({
workspaceId: input.body.workspaceId,
docId: input.body.doc.id,
mode: input.body.doc.mode,
blockId: input.body.doc.blockId,
elementId: input.body.doc.elementId,
})
);
await this.mailer.send({
name: 'Mention',
to: receiver.email,
props: {
user: {
$$userId: input.body.createdByUserId,
},
doc: {
title,
url,
},
},
});
this.logger.log(`Mention email sent to user ${receiver.id}`);
}
async createInvitation(input: InvitationNotificationCreate) {