feat(server): support comment notification type (#12924)

#### PR Dependency Tree


* **PR #12924** 👈
  * **PR #12925**

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Introduced comment and comment mention notifications, including email
notifications when users are mentioned or receive comments on documents.
* Added new email templates for comment and comment mention
notifications.
* Users can now control whether they receive comment-related emails via
a new user setting.

* **Bug Fixes**
  * None.

* **Documentation**
* Updated GraphQL schema documentation to reflect new notification types
and user settings.

* **Refactor**
* Streamlined and enhanced test coverage for notification and user
settings, including comment notifications.

* **Chores**
* Improved test setup and snapshot coverage for user settings and
notifications.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
fengmk2
2025-07-01 21:48:06 +08:00
committed by GitHub
parent 2aa5c13082
commit 7ed72ed1d0
23 changed files with 916 additions and 255 deletions
@@ -7,7 +7,7 @@ import {
} from '@prisma/client';
import { z } from 'zod';
import { PaginationInput } from '../base';
import { Due, PaginationInput } from '../base';
import { BaseModel } from './base';
import { DocMode } from './common';
@@ -16,7 +16,7 @@ export type { Notification };
// #region input
export const ONE_YEAR = 1000 * 60 * 60 * 24 * 365;
export const ONE_YEAR = Due.ms('1y');
const IdSchema = z.string().trim().min(1).max(100);
export const BaseNotificationCreateSchema = z.object({
@@ -96,10 +96,37 @@ export type InvitationReviewDeclinedNotificationCreate = z.input<
typeof InvitationReviewDeclinedNotificationCreateSchema
>;
export const CommentNotificationBodySchema = z.object({
workspaceId: IdSchema,
createdByUserId: IdSchema,
commentId: IdSchema,
replyId: IdSchema.optional(),
doc: MentionDocSchema,
});
export type CommentNotificationBody = z.infer<
typeof CommentNotificationBodySchema
>;
export const CommentNotificationCreateSchema =
BaseNotificationCreateSchema.extend({
body: CommentNotificationBodySchema,
});
export type CommentNotificationCreate = z.input<
typeof CommentNotificationCreateSchema
>;
export const CommentMentionNotificationCreateSchema =
BaseNotificationCreateSchema.extend({
body: CommentNotificationBodySchema,
});
export type UnionNotificationBody =
| MentionNotificationBody
| InvitationNotificationBody
| InvitationReviewDeclinedNotificationBody;
| InvitationReviewDeclinedNotificationBody
| CommentNotificationBody;
// #endregion
@@ -114,10 +141,14 @@ export type InvitationNotification = Notification &
export type InvitationReviewDeclinedNotification = Notification &
z.infer<typeof InvitationReviewDeclinedNotificationCreateSchema>;
export type CommentNotification = Notification &
z.infer<typeof CommentNotificationCreateSchema>;
export type UnionNotification =
| MentionNotification
| InvitationNotification
| InvitationReviewDeclinedNotification;
| InvitationReviewDeclinedNotification
| CommentNotification;
// #endregion
@@ -179,6 +210,40 @@ export class NotificationModel extends BaseModel {
// #endregion
// #region comment
async createComment(input: CommentNotificationCreate) {
const data = CommentNotificationCreateSchema.parse(input);
const type = NotificationType.Comment;
const row = await this.create({
userId: data.userId,
level: data.level,
type,
body: data.body,
});
this.logger.debug(
`Created ${type} notification ${row.id} to user ${data.userId} in workspace ${data.body.workspaceId}`
);
return row as CommentNotification;
}
async createCommentMention(input: CommentNotificationCreate) {
const data = CommentMentionNotificationCreateSchema.parse(input);
const type = NotificationType.CommentMention;
const row = await this.create({
userId: data.userId,
level: data.level,
type,
body: data.body,
});
this.logger.debug(
`Created ${type} notification ${row.id} to user ${data.userId} in workspace ${data.body.workspaceId}`
);
return row as CommentNotification;
}
// #endregion
// #region common
private async create(data: Prisma.NotificationUncheckedCreateInput) {