mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-19 23:37:15 +08:00
feat(server): support read all notifications (#13083)
close AF-2719 #### PR Dependency Tree * **PR #13083** 👈 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** * Added the ability to mark all notifications as read with a single action. * **Bug Fixes** * Ensured notifications marked as read are no longer shown as unread. * **Tests** * Introduced new tests to verify the functionality of marking all notifications as read. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
notificationCountQuery,
|
notificationCountQuery,
|
||||||
NotificationObjectType,
|
NotificationObjectType,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
|
readAllNotificationsMutation,
|
||||||
readNotificationMutation,
|
readNotificationMutation,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
|
|
||||||
@@ -677,3 +678,41 @@ e2e('should list and count notifications', async t => {
|
|||||||
t.is(result3.currentUser!.notifications.edges.length, 0);
|
t.is(result3.currentUser!.notifications.edges.length, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
e2e('should mark all notifications as read', async t => {
|
||||||
|
const { member, owner, workspace } = await init();
|
||||||
|
await app.login(owner);
|
||||||
|
|
||||||
|
await app.gql({
|
||||||
|
query: mentionUserMutation,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
userId: member.id,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
doc: {
|
||||||
|
id: 'doc-id-1',
|
||||||
|
title: 'doc-title-1',
|
||||||
|
blockId: 'block-id-1',
|
||||||
|
mode: DocMode.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.login(member);
|
||||||
|
|
||||||
|
await app.gql({
|
||||||
|
query: readAllNotificationsMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await app.gql({
|
||||||
|
query: listNotificationsQuery,
|
||||||
|
variables: {
|
||||||
|
pagination: {
|
||||||
|
first: 10,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.is(result.currentUser!.notifications.totalCount, 0);
|
||||||
|
});
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ export class UserNotificationResolver {
|
|||||||
await this.service.markAsRead(me.id, notificationId);
|
await this.service.markAsRead(me.id, notificationId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean, {
|
||||||
|
description: 'mark all notifications as read',
|
||||||
|
})
|
||||||
|
async readAllNotifications(@CurrentUser() me: UserType) {
|
||||||
|
await this.service.markAllAsRead(me.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resolver(() => NotificationObjectType)
|
@Resolver(() => NotificationObjectType)
|
||||||
|
|||||||
@@ -399,6 +399,10 @@ export class NotificationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async markAllAsRead(userId: string) {
|
||||||
|
await this.models.notification.markAllAsRead(userId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find notifications by user id, order by createdAt desc
|
* Find notifications by user id, order by createdAt desc
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -430,3 +430,32 @@ test('should create a comment mention notification', async t => {
|
|||||||
t.is(notification.body.commentId, commentId);
|
t.is(notification.body.commentId, commentId);
|
||||||
t.is(notification.body.replyId, replyId);
|
t.is(notification.body.replyId, replyId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should mark all notifications as read', async t => {
|
||||||
|
await models.notification.createMention({
|
||||||
|
userId: user.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
doc: {
|
||||||
|
id: docId,
|
||||||
|
title: 'doc-title',
|
||||||
|
blockId: 'blockId',
|
||||||
|
mode: DocMode.page,
|
||||||
|
},
|
||||||
|
createdByUserId: createdBy.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await models.notification.createInvitation({
|
||||||
|
userId: user.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
createdByUserId: createdBy.id,
|
||||||
|
inviteId: randomUUID(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await models.notification.markAllAsRead(user.id);
|
||||||
|
|
||||||
|
const notifications = await models.notification.findManyByUserId(user.id);
|
||||||
|
t.is(notifications.length, 0);
|
||||||
|
});
|
||||||
|
|||||||
@@ -261,6 +261,18 @@ export class NotificationModel extends BaseModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async markAllAsRead(userId: string) {
|
||||||
|
const { count } = await this.db.notification.updateMany({
|
||||||
|
where: { userId },
|
||||||
|
data: {
|
||||||
|
read: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(
|
||||||
|
`Marked all notifications as read for user ${userId}, count: ${count}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find many notifications by user id, exclude read notifications by default
|
* Find many notifications by user id, exclude read notifications by default
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1236,6 +1236,9 @@ type Mutation {
|
|||||||
"""queue workspace doc embedding"""
|
"""queue workspace doc embedding"""
|
||||||
queueWorkspaceEmbedding(docId: [String!]!, workspaceId: String!): Boolean!
|
queueWorkspaceEmbedding(docId: [String!]!, workspaceId: String!): Boolean!
|
||||||
|
|
||||||
|
"""mark all notifications as read"""
|
||||||
|
readAllNotifications: Boolean!
|
||||||
|
|
||||||
"""mark notification as read"""
|
"""mark notification as read"""
|
||||||
readNotification(id: String!): Boolean!
|
readNotification(id: String!): Boolean!
|
||||||
recoverDoc(guid: String!, timestamp: DateTime!, workspaceId: String!): DateTime!
|
recoverDoc(guid: String!, timestamp: DateTime!, workspaceId: String!): DateTime!
|
||||||
|
|||||||
@@ -1989,6 +1989,14 @@ export const quotaQuery = {
|
|||||||
deprecations: ["'storageQuota' is deprecated: use `UserQuotaType['usedStorageQuota']` instead"],
|
deprecations: ["'storageQuota' is deprecated: use `UserQuotaType['usedStorageQuota']` instead"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const readAllNotificationsMutation = {
|
||||||
|
id: 'readAllNotificationsMutation' as const,
|
||||||
|
op: 'readAllNotifications',
|
||||||
|
query: `mutation readAllNotifications {
|
||||||
|
readAllNotifications
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const readNotificationMutation = {
|
export const readNotificationMutation = {
|
||||||
id: 'readNotificationMutation' as const,
|
id: 'readNotificationMutation' as const,
|
||||||
op: 'readNotification',
|
op: 'readNotification',
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mutation readAllNotifications {
|
||||||
|
readAllNotifications
|
||||||
|
}
|
||||||
@@ -1371,6 +1371,8 @@ export interface Mutation {
|
|||||||
publishPage: DocType;
|
publishPage: DocType;
|
||||||
/** queue workspace doc embedding */
|
/** queue workspace doc embedding */
|
||||||
queueWorkspaceEmbedding: Scalars['Boolean']['output'];
|
queueWorkspaceEmbedding: Scalars['Boolean']['output'];
|
||||||
|
/** mark all notifications as read */
|
||||||
|
readAllNotifications: Scalars['Boolean']['output'];
|
||||||
/** mark notification as read */
|
/** mark notification as read */
|
||||||
readNotification: Scalars['Boolean']['output'];
|
readNotification: Scalars['Boolean']['output'];
|
||||||
recoverDoc: Scalars['DateTime']['output'];
|
recoverDoc: Scalars['DateTime']['output'];
|
||||||
@@ -5218,6 +5220,15 @@ export type QuotaQuery = {
|
|||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ReadAllNotificationsMutationVariables = Exact<{
|
||||||
|
[key: string]: never;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ReadAllNotificationsMutation = {
|
||||||
|
__typename?: 'Mutation';
|
||||||
|
readAllNotifications: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type ReadNotificationMutationVariables = Exact<{
|
export type ReadNotificationMutationVariables = Exact<{
|
||||||
id: Scalars['String']['input'];
|
id: Scalars['String']['input'];
|
||||||
}>;
|
}>;
|
||||||
@@ -6352,6 +6363,11 @@ export type Mutations =
|
|||||||
variables: PublishPageMutationVariables;
|
variables: PublishPageMutationVariables;
|
||||||
response: PublishPageMutation;
|
response: PublishPageMutation;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'readAllNotificationsMutation';
|
||||||
|
variables: ReadAllNotificationsMutationVariables;
|
||||||
|
response: ReadAllNotificationsMutation;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'readNotificationMutation';
|
name: 'readNotificationMutation';
|
||||||
variables: ReadNotificationMutationVariables;
|
variables: ReadNotificationMutationVariables;
|
||||||
|
|||||||
Reference in New Issue
Block a user