mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(server): send invitation review notification (#10418)
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
-- This migration adds more than one value to an enum.
|
||||||
|
-- With PostgreSQL versions 11 and earlier, this is not possible
|
||||||
|
-- in a single migration. This can be worked around by creating
|
||||||
|
-- multiple migrations, each migration adding only one value to
|
||||||
|
-- the enum.
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TYPE "NotificationType" ADD VALUE 'InvitationReviewRequest';
|
||||||
|
ALTER TYPE "NotificationType" ADD VALUE 'InvitationReviewApproved';
|
||||||
|
ALTER TYPE "NotificationType" ADD VALUE 'InvitationReviewDeclined';
|
||||||
@@ -714,6 +714,9 @@ enum NotificationType {
|
|||||||
InvitationAccepted
|
InvitationAccepted
|
||||||
InvitationBlocked
|
InvitationBlocked
|
||||||
InvitationRejected
|
InvitationRejected
|
||||||
|
InvitationReviewRequest
|
||||||
|
InvitationReviewApproved
|
||||||
|
InvitationReviewDeclined
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NotificationLevel {
|
enum NotificationLevel {
|
||||||
|
|||||||
@@ -494,6 +494,18 @@ test('should be able to approve team member', async t => {
|
|||||||
t.is(memberInvite.status, 'UnderReview', 'should be under review');
|
t.is(memberInvite.status, 'UnderReview', 'should be under review');
|
||||||
|
|
||||||
t.true(await approveMember(app, tws.id, member.id));
|
t.true(await approveMember(app, tws.id, member.id));
|
||||||
|
const requestApprovedNotification = app.queue.last(
|
||||||
|
'notification.sendInvitationReviewApproved'
|
||||||
|
);
|
||||||
|
t.truthy(requestApprovedNotification);
|
||||||
|
t.deepEqual(
|
||||||
|
requestApprovedNotification.payload,
|
||||||
|
{
|
||||||
|
inviteId: memberInvite.inviteId,
|
||||||
|
reviewerId: owner.id,
|
||||||
|
},
|
||||||
|
'should send review approved notification'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -627,7 +639,7 @@ test('should be able to invite batch and send notifications', async t => {
|
|||||||
t.truthy(job.payload.inviterId);
|
t.truthy(job.payload.inviterId);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to emit events', async t => {
|
test('should be able to emit events and send notifications', async t => {
|
||||||
const { app, event } = t.context;
|
const { app, event } = t.context;
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -654,24 +666,35 @@ test('should be able to emit events', async t => {
|
|||||||
app.switchUser(owner);
|
app.switchUser(owner);
|
||||||
const { members } = await getWorkspace(app, tws.id);
|
const { members } = await getWorkspace(app, tws.id);
|
||||||
const memberInvite = members.find(m => m.id === user.id)!;
|
const memberInvite = members.find(m => m.id === user.id)!;
|
||||||
|
const requestRequestNotification = app.queue.last(
|
||||||
|
'notification.sendInvitationReviewRequest'
|
||||||
|
);
|
||||||
|
t.truthy(requestRequestNotification);
|
||||||
|
// find admin
|
||||||
|
const admins = await t.context.models.workspaceUser.getAdmins(tws.id);
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
event.emit.lastCall.args,
|
requestRequestNotification.payload,
|
||||||
[
|
{
|
||||||
'workspace.members.reviewRequested',
|
inviteId: memberInvite.inviteId,
|
||||||
{ inviteId: memberInvite.inviteId },
|
reviewerId: admins[0].id,
|
||||||
],
|
},
|
||||||
'should emit review requested event'
|
'should send review request notification'
|
||||||
);
|
);
|
||||||
|
|
||||||
app.switchUser(owner);
|
app.switchUser(owner);
|
||||||
await revokeUser(app, tws.id, user.id);
|
await revokeUser(app, tws.id, user.id);
|
||||||
|
const requestDeclinedNotification = app.queue.last(
|
||||||
|
'notification.sendInvitationReviewDeclined'
|
||||||
|
);
|
||||||
|
t.truthy(requestDeclinedNotification);
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
event.emit.lastCall.args,
|
requestDeclinedNotification.payload,
|
||||||
[
|
{
|
||||||
'workspace.members.requestDeclined',
|
userId: user.id,
|
||||||
{ userId: user.id, workspaceId: tws.id },
|
workspaceId: tws.id,
|
||||||
],
|
reviewerId: owner.id,
|
||||||
'should emit review requested event'
|
},
|
||||||
|
'should send review declined notification'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,3 +114,83 @@ test('should ignore send invitation accepted notification when inviteId not exis
|
|||||||
});
|
});
|
||||||
t.is(spy.callCount, 0);
|
t.is(spy.callCount, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should create invitation review request notification', async t => {
|
||||||
|
const { notificationJob, notificationService, models } = t.context;
|
||||||
|
const invite = await models.workspaceUser.set(
|
||||||
|
workspace.id,
|
||||||
|
member.id,
|
||||||
|
WorkspaceRole.Collaborator,
|
||||||
|
WorkspaceMemberStatus.Pending
|
||||||
|
);
|
||||||
|
const spy = Sinon.spy(notificationService, 'createInvitationReviewRequest');
|
||||||
|
await notificationJob.sendInvitationReviewRequest({
|
||||||
|
reviewerId: owner.id,
|
||||||
|
inviteId: invite.id,
|
||||||
|
});
|
||||||
|
t.is(spy.callCount, 1);
|
||||||
|
t.is(spy.firstCall.args[0].userId, owner.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.workspaceId, workspace.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.createdByUserId, member.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.inviteId, invite.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore send invitation review request notification when inviteId not exists', async t => {
|
||||||
|
const { notificationJob, notificationService } = t.context;
|
||||||
|
const spy = Sinon.spy(notificationService, 'createInvitationReviewRequest');
|
||||||
|
await notificationJob.sendInvitationReviewRequest({
|
||||||
|
reviewerId: owner.id,
|
||||||
|
inviteId: `not-exists-${randomUUID()}`,
|
||||||
|
});
|
||||||
|
t.is(spy.callCount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create invitation review approved notification', async t => {
|
||||||
|
const { notificationJob, notificationService, models } = t.context;
|
||||||
|
const invite = await models.workspaceUser.set(
|
||||||
|
workspace.id,
|
||||||
|
member.id,
|
||||||
|
WorkspaceRole.Collaborator,
|
||||||
|
WorkspaceMemberStatus.Pending
|
||||||
|
);
|
||||||
|
const spy = Sinon.spy(notificationService, 'createInvitationReviewApproved');
|
||||||
|
await notificationJob.sendInvitationReviewApproved({
|
||||||
|
reviewerId: owner.id,
|
||||||
|
inviteId: invite.id,
|
||||||
|
});
|
||||||
|
t.is(spy.callCount, 1);
|
||||||
|
t.is(spy.firstCall.args[0].userId, member.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.workspaceId, workspace.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.createdByUserId, owner.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.inviteId, invite.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore send invitation review approved notification when inviteId not exists', async t => {
|
||||||
|
const { notificationJob, notificationService } = t.context;
|
||||||
|
const spy = Sinon.spy(notificationService, 'createInvitationReviewApproved');
|
||||||
|
await notificationJob.sendInvitationReviewApproved({
|
||||||
|
reviewerId: owner.id,
|
||||||
|
inviteId: `not-exists-${randomUUID()}`,
|
||||||
|
});
|
||||||
|
t.is(spy.callCount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create invitation review declined notification', async t => {
|
||||||
|
const { notificationJob, notificationService, models } = t.context;
|
||||||
|
await models.workspaceUser.set(
|
||||||
|
workspace.id,
|
||||||
|
member.id,
|
||||||
|
WorkspaceRole.Collaborator,
|
||||||
|
WorkspaceMemberStatus.Pending
|
||||||
|
);
|
||||||
|
const spy = Sinon.spy(notificationService, 'createInvitationReviewDeclined');
|
||||||
|
await notificationJob.sendInvitationReviewDeclined({
|
||||||
|
reviewerId: owner.id,
|
||||||
|
userId: member.id,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
t.is(spy.callCount, 1);
|
||||||
|
t.is(spy.firstCall.args[0].userId, member.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.workspaceId, workspace.id);
|
||||||
|
t.is(spy.firstCall.args[0].body.createdByUserId, owner.id);
|
||||||
|
});
|
||||||
|
|||||||
@@ -224,6 +224,139 @@ test('should create invitation rejected notification', async t => {
|
|||||||
t.is(notification!.body.inviteId, inviteId);
|
t.is(notification!.body.inviteId, inviteId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should create invitation review request notification if user is not an active member', async t => {
|
||||||
|
const { notificationService, models } = t.context;
|
||||||
|
const inviteId = randomUUID();
|
||||||
|
mock.method(models.workspaceUser, 'getActive', async () => null);
|
||||||
|
const notification = await notificationService.createInvitationReviewRequest({
|
||||||
|
userId: owner.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
createdByUserId: member.id,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.truthy(notification);
|
||||||
|
t.is(notification!.type, NotificationType.InvitationReviewRequest);
|
||||||
|
t.is(notification!.userId, owner.id);
|
||||||
|
t.is(notification!.body.workspaceId, workspace.id);
|
||||||
|
t.is(notification!.body.createdByUserId, member.id);
|
||||||
|
t.is(notification!.body.inviteId, inviteId);
|
||||||
|
|
||||||
|
// should send email
|
||||||
|
const invitationReviewRequestMail = t.context.module.mails.last(
|
||||||
|
'LinkInvitationReviewRequest'
|
||||||
|
);
|
||||||
|
t.is(invitationReviewRequestMail.to, owner.email);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not create invitation review request notification if user is an active member', async t => {
|
||||||
|
const { notificationService, models } = t.context;
|
||||||
|
const inviteId = randomUUID();
|
||||||
|
mock.method(models.workspaceUser, 'getActive', async () => ({
|
||||||
|
id: inviteId,
|
||||||
|
}));
|
||||||
|
const notification = await notificationService.createInvitationReviewRequest({
|
||||||
|
userId: owner.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
createdByUserId: member.id,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.is(notification, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create invitation review approved notification if user is an active member', async t => {
|
||||||
|
const { notificationService, models } = t.context;
|
||||||
|
const inviteId = randomUUID();
|
||||||
|
mock.method(models.workspaceUser, 'getActive', async () => ({
|
||||||
|
id: inviteId,
|
||||||
|
}));
|
||||||
|
const notification = await notificationService.createInvitationReviewApproved(
|
||||||
|
{
|
||||||
|
userId: member.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
createdByUserId: owner.id,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.truthy(notification);
|
||||||
|
t.is(notification!.type, NotificationType.InvitationReviewApproved);
|
||||||
|
t.is(notification!.userId, member.id);
|
||||||
|
t.is(notification!.body.workspaceId, workspace.id);
|
||||||
|
t.is(notification!.body.createdByUserId, owner.id);
|
||||||
|
t.is(notification!.body.inviteId, inviteId);
|
||||||
|
|
||||||
|
// should send email
|
||||||
|
const invitationReviewApprovedMail = t.context.module.mails.last(
|
||||||
|
'LinkInvitationApprove'
|
||||||
|
);
|
||||||
|
t.is(invitationReviewApprovedMail.to, member.email);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not create invitation review approved notification if user is not an active member', async t => {
|
||||||
|
const { notificationService, models } = t.context;
|
||||||
|
const inviteId = randomUUID();
|
||||||
|
mock.method(models.workspaceUser, 'getActive', async () => null);
|
||||||
|
const notification = await notificationService.createInvitationReviewApproved(
|
||||||
|
{
|
||||||
|
userId: owner.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
createdByUserId: member.id,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.is(notification, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create invitation review declined notification if user is not an active member', async t => {
|
||||||
|
const { notificationService, models } = t.context;
|
||||||
|
mock.method(models.workspaceUser, 'getActive', async () => null);
|
||||||
|
const notification = await notificationService.createInvitationReviewDeclined(
|
||||||
|
{
|
||||||
|
userId: member.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
createdByUserId: owner.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.truthy(notification);
|
||||||
|
t.is(notification!.type, NotificationType.InvitationReviewDeclined);
|
||||||
|
t.is(notification!.userId, member.id);
|
||||||
|
t.is(notification!.body.workspaceId, workspace.id);
|
||||||
|
t.is(notification!.body.createdByUserId, owner.id);
|
||||||
|
|
||||||
|
// should send email
|
||||||
|
const invitationReviewDeclinedMail = t.context.module.mails.last(
|
||||||
|
'LinkInvitationDecline'
|
||||||
|
);
|
||||||
|
t.is(invitationReviewDeclinedMail.to, member.email);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not create invitation review declined notification if user is an active member', async t => {
|
||||||
|
const { notificationService, models } = t.context;
|
||||||
|
const inviteId = randomUUID();
|
||||||
|
mock.method(models.workspaceUser, 'getActive', async () => ({
|
||||||
|
id: inviteId,
|
||||||
|
}));
|
||||||
|
const notification = await notificationService.createInvitationReviewDeclined(
|
||||||
|
{
|
||||||
|
userId: owner.id,
|
||||||
|
body: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
createdByUserId: member.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
t.is(notification, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
test('should clean expired notifications', async t => {
|
test('should clean expired notifications', async t => {
|
||||||
const { notificationService } = t.context;
|
const { notificationService } = t.context;
|
||||||
await notificationService.createInvitation({
|
await notificationService.createInvitation({
|
||||||
|
|||||||
@@ -16,6 +16,19 @@ declare global {
|
|||||||
inviterId: string;
|
inviterId: string;
|
||||||
inviteId: string;
|
inviteId: string;
|
||||||
};
|
};
|
||||||
|
'notification.sendInvitationReviewRequest': {
|
||||||
|
reviewerId: string;
|
||||||
|
inviteId: string;
|
||||||
|
};
|
||||||
|
'notification.sendInvitationReviewApproved': {
|
||||||
|
reviewerId: string;
|
||||||
|
inviteId: string;
|
||||||
|
};
|
||||||
|
'notification.sendInvitationReviewDeclined': {
|
||||||
|
reviewerId: string;
|
||||||
|
userId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,4 +93,57 @@ export class NotificationJob {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnJob('notification.sendInvitationReviewRequest')
|
||||||
|
async sendInvitationReviewRequest({
|
||||||
|
reviewerId,
|
||||||
|
inviteId,
|
||||||
|
}: Jobs['notification.sendInvitationReviewRequest']) {
|
||||||
|
const invite = await this.models.workspaceUser.getById(inviteId);
|
||||||
|
if (!invite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.service.createInvitationReviewRequest({
|
||||||
|
userId: reviewerId,
|
||||||
|
body: {
|
||||||
|
workspaceId: invite.workspaceId,
|
||||||
|
createdByUserId: invite.userId,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnJob('notification.sendInvitationReviewApproved')
|
||||||
|
async sendInvitationReviewApproved({
|
||||||
|
reviewerId,
|
||||||
|
inviteId,
|
||||||
|
}: Jobs['notification.sendInvitationReviewApproved']) {
|
||||||
|
const invite = await this.models.workspaceUser.getById(inviteId);
|
||||||
|
if (!invite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.service.createInvitationReviewApproved({
|
||||||
|
userId: invite.userId,
|
||||||
|
body: {
|
||||||
|
workspaceId: invite.workspaceId,
|
||||||
|
createdByUserId: reviewerId,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnJob('notification.sendInvitationReviewDeclined')
|
||||||
|
async sendInvitationReviewDeclined({
|
||||||
|
reviewerId,
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
}: Jobs['notification.sendInvitationReviewDeclined']) {
|
||||||
|
await this.service.createInvitationReviewDeclined({
|
||||||
|
userId,
|
||||||
|
body: {
|
||||||
|
workspaceId,
|
||||||
|
createdByUserId: reviewerId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
DEFAULT_WORKSPACE_NAME,
|
DEFAULT_WORKSPACE_NAME,
|
||||||
InvitationNotificationCreate,
|
InvitationNotificationCreate,
|
||||||
|
InvitationReviewDeclinedNotificationCreate,
|
||||||
MentionNotification,
|
MentionNotification,
|
||||||
MentionNotificationCreate,
|
MentionNotificationCreate,
|
||||||
Models,
|
Models,
|
||||||
@@ -191,6 +192,126 @@ export class NotificationService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createInvitationReviewRequest(input: InvitationNotificationCreate) {
|
||||||
|
const workspaceId = input.body.workspaceId;
|
||||||
|
const userId = input.userId;
|
||||||
|
if (await this.isActiveWorkspaceUser(workspaceId, userId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.ensureWorkspaceContentExists(workspaceId);
|
||||||
|
const notification = await this.models.notification.createInvitation(
|
||||||
|
input,
|
||||||
|
NotificationType.InvitationReviewRequest
|
||||||
|
);
|
||||||
|
await this.sendInvitationReviewRequestEmail(input);
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendInvitationReviewRequestEmail(
|
||||||
|
input: InvitationNotificationCreate
|
||||||
|
) {
|
||||||
|
const inviteeUserId = input.body.createdByUserId;
|
||||||
|
const reviewerUserId = input.userId;
|
||||||
|
const workspaceId = input.body.workspaceId;
|
||||||
|
const reviewer = await this.models.user.getWorkspaceUser(reviewerUserId);
|
||||||
|
if (!reviewer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.mailer.send({
|
||||||
|
name: 'LinkInvitationReviewRequest',
|
||||||
|
to: reviewer.email,
|
||||||
|
props: {
|
||||||
|
user: {
|
||||||
|
$$userId: inviteeUserId,
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
$$workspaceId: workspaceId,
|
||||||
|
},
|
||||||
|
url: this.url.link(`/workspace/${workspaceId}`),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(
|
||||||
|
`Invitation review request email sent to user ${reviewer.id} for workspace ${workspaceId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInvitationReviewApproved(input: InvitationNotificationCreate) {
|
||||||
|
const workspaceId = input.body.workspaceId;
|
||||||
|
const userId = input.userId;
|
||||||
|
if (!(await this.isActiveWorkspaceUser(workspaceId, userId))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.ensureWorkspaceContentExists(workspaceId);
|
||||||
|
const notification = await this.models.notification.createInvitation(
|
||||||
|
input,
|
||||||
|
NotificationType.InvitationReviewApproved
|
||||||
|
);
|
||||||
|
await this.sendInvitationReviewApprovedEmail(input);
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendInvitationReviewApprovedEmail(
|
||||||
|
input: InvitationNotificationCreate
|
||||||
|
) {
|
||||||
|
const workspaceId = input.body.workspaceId;
|
||||||
|
const receiverUserId = input.userId;
|
||||||
|
const receiver = await this.models.user.getWorkspaceUser(receiverUserId);
|
||||||
|
if (!receiver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.mailer.send({
|
||||||
|
name: 'LinkInvitationApprove',
|
||||||
|
to: receiver.email,
|
||||||
|
props: {
|
||||||
|
workspace: {
|
||||||
|
$$workspaceId: workspaceId,
|
||||||
|
},
|
||||||
|
url: this.url.link(`/workspace/${workspaceId}`),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(
|
||||||
|
`Invitation review approved email sent to user ${receiver.id} for workspace ${workspaceId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInvitationReviewDeclined(
|
||||||
|
input: InvitationReviewDeclinedNotificationCreate
|
||||||
|
) {
|
||||||
|
const workspaceId = input.body.workspaceId;
|
||||||
|
const userId = input.userId;
|
||||||
|
if (await this.isActiveWorkspaceUser(workspaceId, userId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.ensureWorkspaceContentExists(workspaceId);
|
||||||
|
const notification =
|
||||||
|
await this.models.notification.createInvitationReviewDeclined(input);
|
||||||
|
await this.sendInvitationReviewDeclinedEmail(input);
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendInvitationReviewDeclinedEmail(
|
||||||
|
input: InvitationReviewDeclinedNotificationCreate
|
||||||
|
) {
|
||||||
|
const workspaceId = input.body.workspaceId;
|
||||||
|
const receiverUserId = input.userId;
|
||||||
|
const receiver = await this.models.user.getWorkspaceUser(receiverUserId);
|
||||||
|
if (!receiver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.mailer.send({
|
||||||
|
name: 'LinkInvitationDecline',
|
||||||
|
to: receiver.email,
|
||||||
|
props: {
|
||||||
|
workspace: {
|
||||||
|
$$workspaceId: workspaceId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(
|
||||||
|
`Invitation review declined email sent to user ${receiver.id} for workspace ${workspaceId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async ensureWorkspaceContentExists(workspaceId: string) {
|
private async ensureWorkspaceContentExists(workspaceId: string) {
|
||||||
await this.docReader.getWorkspaceContent(workspaceId);
|
await this.docReader.getWorkspaceContent(workspaceId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,31 +100,38 @@ export class MentionNotificationBodyType extends BaseNotificationBodyType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class InvitationNotificationBodyType
|
export abstract class InvitationBaseNotificationBodyType extends BaseNotificationBodyType {
|
||||||
extends BaseNotificationBodyType
|
|
||||||
implements Partial<InvitationNotificationBody>
|
|
||||||
{
|
|
||||||
@Field(() => ID)
|
@Field(() => ID)
|
||||||
inviteId!: string;
|
inviteId!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class InvitationNotificationBodyType
|
||||||
|
extends InvitationBaseNotificationBodyType
|
||||||
|
implements Partial<InvitationNotificationBody> {}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class InvitationAcceptedNotificationBodyType
|
export class InvitationAcceptedNotificationBodyType
|
||||||
extends BaseNotificationBodyType
|
extends InvitationBaseNotificationBodyType
|
||||||
implements Partial<InvitationNotificationBody>
|
implements Partial<InvitationNotificationBody> {}
|
||||||
{
|
|
||||||
@Field(() => String)
|
|
||||||
inviteId!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class InvitationBlockedNotificationBodyType
|
export class InvitationBlockedNotificationBodyType
|
||||||
extends BaseNotificationBodyType
|
extends InvitationBaseNotificationBodyType
|
||||||
implements Partial<InvitationNotificationBody>
|
implements Partial<InvitationNotificationBody> {}
|
||||||
{
|
|
||||||
@Field(() => String)
|
@ObjectType()
|
||||||
inviteId!: string;
|
export class InvitationReviewRequestNotificationBodyType
|
||||||
}
|
extends InvitationBaseNotificationBodyType
|
||||||
|
implements Partial<InvitationNotificationBody> {}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class InvitationReviewApprovedNotificationBodyType
|
||||||
|
extends InvitationBaseNotificationBodyType
|
||||||
|
implements Partial<InvitationNotificationBody> {}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class InvitationReviewDeclinedNotificationBodyType extends BaseNotificationBodyType {}
|
||||||
|
|
||||||
export const UnionNotificationBodyType = createUnionType({
|
export const UnionNotificationBodyType = createUnionType({
|
||||||
name: 'UnionNotificationBodyType',
|
name: 'UnionNotificationBodyType',
|
||||||
@@ -134,6 +141,9 @@ export const UnionNotificationBodyType = createUnionType({
|
|||||||
InvitationNotificationBodyType,
|
InvitationNotificationBodyType,
|
||||||
InvitationAcceptedNotificationBodyType,
|
InvitationAcceptedNotificationBodyType,
|
||||||
InvitationBlockedNotificationBodyType,
|
InvitationBlockedNotificationBodyType,
|
||||||
|
InvitationReviewRequestNotificationBodyType,
|
||||||
|
InvitationReviewApprovedNotificationBodyType,
|
||||||
|
InvitationReviewDeclinedNotificationBodyType,
|
||||||
] as const,
|
] as const,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,38 +11,6 @@ export class WorkspaceEvents {
|
|||||||
private readonly models: Models
|
private readonly models: Models
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@OnEvent('workspace.members.reviewRequested')
|
|
||||||
async onReviewRequested({
|
|
||||||
inviteId,
|
|
||||||
}: Events['workspace.members.reviewRequested']) {
|
|
||||||
// send review request mail to owner and admin
|
|
||||||
await this.workspaceService.sendReviewRequestedEmail(inviteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnEvent('workspace.members.requestApproved')
|
|
||||||
async onApproveRequest({
|
|
||||||
inviteId,
|
|
||||||
}: Events['workspace.members.requestApproved']) {
|
|
||||||
// send approve mail
|
|
||||||
await this.workspaceService.sendReviewApproveEmail(inviteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnEvent('workspace.members.requestDeclined')
|
|
||||||
async onDeclineRequest({
|
|
||||||
userId,
|
|
||||||
workspaceId,
|
|
||||||
}: Events['workspace.members.requestDeclined']) {
|
|
||||||
const user = await this.models.user.getWorkspaceUser(userId);
|
|
||||||
if (!user) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// send decline mail
|
|
||||||
await this.workspaceService.sendReviewDeclinedEmail(
|
|
||||||
user.email,
|
|
||||||
workspaceId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnEvent('workspace.members.roleChanged')
|
@OnEvent('workspace.members.roleChanged')
|
||||||
async onRoleChanged({
|
async onRoleChanged({
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export class WorkspaceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendReviewRequestedEmail(inviteId: string) {
|
async sendReviewRequestNotification(inviteId: string) {
|
||||||
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
|
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
|
||||||
if (!inviteeUserId) {
|
if (!inviteeUserId) {
|
||||||
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
|
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
|
||||||
@@ -159,59 +159,31 @@ export class WorkspaceService {
|
|||||||
const admins = await this.models.workspaceUser.getAdmins(workspaceId);
|
const admins = await this.models.workspaceUser.getAdmins(workspaceId);
|
||||||
|
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
[owner, ...admins].map(async receiver => {
|
[owner, ...admins].map(async reviewer => {
|
||||||
await this.mailer.send({
|
await this.queue.add('notification.sendInvitationReviewRequest', {
|
||||||
name: 'LinkInvitationReviewRequest',
|
reviewerId: reviewer.id,
|
||||||
to: receiver.email,
|
inviteId,
|
||||||
props: {
|
|
||||||
user: {
|
|
||||||
$$userId: inviteeUserId,
|
|
||||||
},
|
|
||||||
workspace: {
|
|
||||||
$$workspaceId: workspaceId,
|
|
||||||
},
|
|
||||||
url: this.url.link(`/workspace/${workspaceId}`),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendReviewApproveEmail(inviteId: string) {
|
async sendReviewApprovedNotification(inviteId: string, reviewerId: string) {
|
||||||
const invitation = await this.models.workspaceUser.getById(inviteId);
|
await this.queue.add('notification.sendInvitationReviewApproved', {
|
||||||
if (!invitation) {
|
reviewerId,
|
||||||
this.logger.warn(`Invitation not found for inviteId: ${inviteId}`);
|
inviteId,
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await this.models.user.getWorkspaceUser(invitation.userId);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
this.logger.warn(`Invitee user not found for inviteId: ${inviteId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.mailer.send({
|
|
||||||
name: 'LinkInvitationApprove',
|
|
||||||
to: user.email,
|
|
||||||
props: {
|
|
||||||
workspace: {
|
|
||||||
$$workspaceId: invitation.workspaceId,
|
|
||||||
},
|
|
||||||
url: this.url.link(`/workspace/${invitation.workspaceId}`),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendReviewDeclinedEmail(email: string, workspaceId: string) {
|
async sendReviewDeclinedNotification(
|
||||||
await this.mailer.send({
|
userId: string,
|
||||||
name: 'LinkInvitationDecline',
|
workspaceId: string,
|
||||||
to: email,
|
reviewerId: string
|
||||||
props: {
|
) {
|
||||||
workspace: {
|
await this.queue.add('notification.sendInvitationReviewDeclined', {
|
||||||
$$workspaceId: workspaceId,
|
reviewerId,
|
||||||
},
|
userId,
|
||||||
},
|
workspaceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,12 +223,12 @@ export class TeamWorkspaceResolver {
|
|||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async approveMember(
|
async approveMember(
|
||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() me: CurrentUser,
|
||||||
@Args('workspaceId') workspaceId: string,
|
@Args('workspaceId') workspaceId: string,
|
||||||
@Args('userId') userId: string
|
@Args('userId') userId: string
|
||||||
) {
|
) {
|
||||||
await this.ac
|
await this.ac
|
||||||
.user(user.id)
|
.user(me.id)
|
||||||
.workspace(workspaceId)
|
.workspace(workspaceId)
|
||||||
.assert('Workspace.Users.Manage');
|
.assert('Workspace.Users.Manage');
|
||||||
|
|
||||||
@@ -242,9 +242,10 @@ export class TeamWorkspaceResolver {
|
|||||||
WorkspaceMemberStatus.Accepted
|
WorkspaceMemberStatus.Accepted
|
||||||
);
|
);
|
||||||
|
|
||||||
this.event.emit('workspace.members.requestApproved', {
|
await this.workspaceService.sendReviewApprovedNotification(
|
||||||
inviteId: result.id,
|
result.id,
|
||||||
});
|
me.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -529,11 +529,11 @@ export class WorkspaceResolver {
|
|||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async revoke(
|
async revoke(
|
||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() me: CurrentUser,
|
||||||
@Args('workspaceId') workspaceId: string,
|
@Args('workspaceId') workspaceId: string,
|
||||||
@Args('userId') userId: string
|
@Args('userId') userId: string
|
||||||
) {
|
) {
|
||||||
if (userId === user.id) {
|
if (userId === me.id) {
|
||||||
throw new CanNotRevokeYourself();
|
throw new CanNotRevokeYourself();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +544,7 @@ export class WorkspaceResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.ac
|
await this.ac
|
||||||
.user(user.id)
|
.user(me.id)
|
||||||
.workspace(workspaceId)
|
.workspace(workspaceId)
|
||||||
.assert(
|
.assert(
|
||||||
role.type === WorkspaceRole.Admin
|
role.type === WorkspaceRole.Admin
|
||||||
@@ -562,10 +562,11 @@ export class WorkspaceResolver {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (role.status === WorkspaceMemberStatus.UnderReview) {
|
if (role.status === WorkspaceMemberStatus.UnderReview) {
|
||||||
this.event.emit('workspace.members.requestDeclined', {
|
await this.workspaceService.sendReviewDeclinedNotification(
|
||||||
userId,
|
userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
me.id
|
||||||
|
);
|
||||||
} else if (role.status === WorkspaceMemberStatus.Accepted) {
|
} else if (role.status === WorkspaceMemberStatus.Accepted) {
|
||||||
this.event.emit('workspace.members.removed', {
|
this.event.emit('workspace.members.removed', {
|
||||||
userId,
|
userId,
|
||||||
@@ -617,9 +618,7 @@ export class WorkspaceResolver {
|
|||||||
WorkspaceRole.Collaborator,
|
WorkspaceRole.Collaborator,
|
||||||
WorkspaceMemberStatus.UnderReview
|
WorkspaceMemberStatus.UnderReview
|
||||||
);
|
);
|
||||||
this.event.emit('workspace.members.reviewRequested', {
|
await this.workspaceService.sendReviewRequestNotification(invite.id);
|
||||||
inviteId: invite.id,
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
const isTeam =
|
const isTeam =
|
||||||
|
|||||||
@@ -78,9 +78,28 @@ export type InvitationNotificationCreate = z.input<
|
|||||||
typeof InvitationNotificationCreateSchema
|
typeof InvitationNotificationCreateSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const InvitationReviewDeclinedNotificationBodySchema = z.object({
|
||||||
|
workspaceId: IdSchema,
|
||||||
|
createdByUserId: IdSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InvitationReviewDeclinedNotificationBody = z.infer<
|
||||||
|
typeof InvitationReviewDeclinedNotificationBodySchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const InvitationReviewDeclinedNotificationCreateSchema =
|
||||||
|
BaseNotificationCreateSchema.extend({
|
||||||
|
body: InvitationReviewDeclinedNotificationBodySchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InvitationReviewDeclinedNotificationCreate = z.input<
|
||||||
|
typeof InvitationReviewDeclinedNotificationCreateSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export type UnionNotificationBody =
|
export type UnionNotificationBody =
|
||||||
| MentionNotificationBody
|
| MentionNotificationBody
|
||||||
| InvitationNotificationBody;
|
| InvitationNotificationBody
|
||||||
|
| InvitationReviewDeclinedNotificationBody;
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
@@ -92,7 +111,13 @@ export type MentionNotification = Notification &
|
|||||||
export type InvitationNotification = Notification &
|
export type InvitationNotification = Notification &
|
||||||
z.infer<typeof InvitationNotificationCreateSchema>;
|
z.infer<typeof InvitationNotificationCreateSchema>;
|
||||||
|
|
||||||
export type UnionNotification = MentionNotification | InvitationNotification;
|
export type InvitationReviewDeclinedNotification = Notification &
|
||||||
|
z.infer<typeof InvitationReviewDeclinedNotificationCreateSchema>;
|
||||||
|
|
||||||
|
export type UnionNotification =
|
||||||
|
| MentionNotification
|
||||||
|
| InvitationNotification
|
||||||
|
| InvitationReviewDeclinedNotification;
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
@@ -135,6 +160,23 @@ export class NotificationModel extends BaseModel {
|
|||||||
return row as InvitationNotification;
|
return row as InvitationNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createInvitationReviewDeclined(
|
||||||
|
input: InvitationReviewDeclinedNotificationCreate
|
||||||
|
) {
|
||||||
|
const data = InvitationReviewDeclinedNotificationCreateSchema.parse(input);
|
||||||
|
const type = NotificationType.InvitationReviewDeclined;
|
||||||
|
const row = await this.create({
|
||||||
|
userId: data.userId,
|
||||||
|
level: data.level,
|
||||||
|
type,
|
||||||
|
body: data.body,
|
||||||
|
});
|
||||||
|
this.logger.log(
|
||||||
|
`Created ${type} notification ${row.id} to user ${data.userId} in workspace ${data.body.workspaceId}`
|
||||||
|
);
|
||||||
|
return row as InvitationReviewDeclinedNotification;
|
||||||
|
}
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region common
|
// #region common
|
||||||
|
|||||||
@@ -26,16 +26,6 @@ declare global {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
count: number;
|
count: number;
|
||||||
};
|
};
|
||||||
'workspace.members.reviewRequested': {
|
|
||||||
inviteId: string;
|
|
||||||
};
|
|
||||||
'workspace.members.requestApproved': {
|
|
||||||
inviteId: string;
|
|
||||||
};
|
|
||||||
'workspace.members.requestDeclined': {
|
|
||||||
userId: string;
|
|
||||||
workspaceId: string;
|
|
||||||
};
|
|
||||||
'workspace.members.removed': {
|
'workspace.members.removed': {
|
||||||
userId: string;
|
userId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ type InvitationAcceptedNotificationBodyType {
|
|||||||
The user who created the notification, maybe null when user is deleted or sent by system
|
The user who created the notification, maybe null when user is deleted or sent by system
|
||||||
"""
|
"""
|
||||||
createdByUser: PublicUserType
|
createdByUser: PublicUserType
|
||||||
inviteId: String!
|
inviteId: ID!
|
||||||
|
|
||||||
"""The type of the notification"""
|
"""The type of the notification"""
|
||||||
type: NotificationType!
|
type: NotificationType!
|
||||||
@@ -636,7 +636,7 @@ type InvitationBlockedNotificationBodyType {
|
|||||||
The user who created the notification, maybe null when user is deleted or sent by system
|
The user who created the notification, maybe null when user is deleted or sent by system
|
||||||
"""
|
"""
|
||||||
createdByUser: PublicUserType
|
createdByUser: PublicUserType
|
||||||
inviteId: String!
|
inviteId: ID!
|
||||||
|
|
||||||
"""The type of the notification"""
|
"""The type of the notification"""
|
||||||
type: NotificationType!
|
type: NotificationType!
|
||||||
@@ -655,6 +655,41 @@ type InvitationNotificationBodyType {
|
|||||||
workspace: NotificationWorkspaceType
|
workspace: NotificationWorkspaceType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InvitationReviewApprovedNotificationBodyType {
|
||||||
|
"""
|
||||||
|
The user who created the notification, maybe null when user is deleted or sent by system
|
||||||
|
"""
|
||||||
|
createdByUser: PublicUserType
|
||||||
|
inviteId: ID!
|
||||||
|
|
||||||
|
"""The type of the notification"""
|
||||||
|
type: NotificationType!
|
||||||
|
workspace: NotificationWorkspaceType
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvitationReviewDeclinedNotificationBodyType {
|
||||||
|
"""
|
||||||
|
The user who created the notification, maybe null when user is deleted or sent by system
|
||||||
|
"""
|
||||||
|
createdByUser: PublicUserType
|
||||||
|
|
||||||
|
"""The type of the notification"""
|
||||||
|
type: NotificationType!
|
||||||
|
workspace: NotificationWorkspaceType
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvitationReviewRequestNotificationBodyType {
|
||||||
|
"""
|
||||||
|
The user who created the notification, maybe null when user is deleted or sent by system
|
||||||
|
"""
|
||||||
|
createdByUser: PublicUserType
|
||||||
|
inviteId: ID!
|
||||||
|
|
||||||
|
"""The type of the notification"""
|
||||||
|
type: NotificationType!
|
||||||
|
workspace: NotificationWorkspaceType
|
||||||
|
}
|
||||||
|
|
||||||
type InvitationType {
|
type InvitationType {
|
||||||
"""Invitee information"""
|
"""Invitee information"""
|
||||||
invitee: UserType!
|
invitee: UserType!
|
||||||
@@ -1047,6 +1082,9 @@ enum NotificationType {
|
|||||||
InvitationAccepted
|
InvitationAccepted
|
||||||
InvitationBlocked
|
InvitationBlocked
|
||||||
InvitationRejected
|
InvitationRejected
|
||||||
|
InvitationReviewApproved
|
||||||
|
InvitationReviewDeclined
|
||||||
|
InvitationReviewRequest
|
||||||
Mention
|
Mention
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1435,7 +1473,7 @@ type TranscriptionResultType {
|
|||||||
transcription: [TranscriptionItemType!]
|
transcription: [TranscriptionItemType!]
|
||||||
}
|
}
|
||||||
|
|
||||||
union UnionNotificationBodyType = InvitationAcceptedNotificationBodyType | InvitationBlockedNotificationBodyType | InvitationNotificationBodyType | MentionNotificationBodyType
|
union UnionNotificationBodyType = InvitationAcceptedNotificationBodyType | InvitationBlockedNotificationBodyType | InvitationNotificationBodyType | InvitationReviewApprovedNotificationBodyType | InvitationReviewDeclinedNotificationBodyType | InvitationReviewRequestNotificationBodyType | MentionNotificationBodyType
|
||||||
|
|
||||||
type UnknownOauthProviderDataType {
|
type UnknownOauthProviderDataType {
|
||||||
name: String!
|
name: String!
|
||||||
|
|||||||
@@ -776,7 +776,7 @@ export interface InvitationAcceptedNotificationBodyType {
|
|||||||
__typename?: 'InvitationAcceptedNotificationBodyType';
|
__typename?: 'InvitationAcceptedNotificationBodyType';
|
||||||
/** The user who created the notification, maybe null when user is deleted or sent by system */
|
/** The user who created the notification, maybe null when user is deleted or sent by system */
|
||||||
createdByUser: Maybe<PublicUserType>;
|
createdByUser: Maybe<PublicUserType>;
|
||||||
inviteId: Scalars['String']['output'];
|
inviteId: Scalars['ID']['output'];
|
||||||
/** The type of the notification */
|
/** The type of the notification */
|
||||||
type: NotificationType;
|
type: NotificationType;
|
||||||
workspace: Maybe<NotificationWorkspaceType>;
|
workspace: Maybe<NotificationWorkspaceType>;
|
||||||
@@ -786,7 +786,7 @@ export interface InvitationBlockedNotificationBodyType {
|
|||||||
__typename?: 'InvitationBlockedNotificationBodyType';
|
__typename?: 'InvitationBlockedNotificationBodyType';
|
||||||
/** The user who created the notification, maybe null when user is deleted or sent by system */
|
/** The user who created the notification, maybe null when user is deleted or sent by system */
|
||||||
createdByUser: Maybe<PublicUserType>;
|
createdByUser: Maybe<PublicUserType>;
|
||||||
inviteId: Scalars['String']['output'];
|
inviteId: Scalars['ID']['output'];
|
||||||
/** The type of the notification */
|
/** The type of the notification */
|
||||||
type: NotificationType;
|
type: NotificationType;
|
||||||
workspace: Maybe<NotificationWorkspaceType>;
|
workspace: Maybe<NotificationWorkspaceType>;
|
||||||
@@ -802,6 +802,35 @@ export interface InvitationNotificationBodyType {
|
|||||||
workspace: Maybe<NotificationWorkspaceType>;
|
workspace: Maybe<NotificationWorkspaceType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InvitationReviewApprovedNotificationBodyType {
|
||||||
|
__typename?: 'InvitationReviewApprovedNotificationBodyType';
|
||||||
|
/** The user who created the notification, maybe null when user is deleted or sent by system */
|
||||||
|
createdByUser: Maybe<PublicUserType>;
|
||||||
|
inviteId: Scalars['ID']['output'];
|
||||||
|
/** The type of the notification */
|
||||||
|
type: NotificationType;
|
||||||
|
workspace: Maybe<NotificationWorkspaceType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvitationReviewDeclinedNotificationBodyType {
|
||||||
|
__typename?: 'InvitationReviewDeclinedNotificationBodyType';
|
||||||
|
/** The user who created the notification, maybe null when user is deleted or sent by system */
|
||||||
|
createdByUser: Maybe<PublicUserType>;
|
||||||
|
/** The type of the notification */
|
||||||
|
type: NotificationType;
|
||||||
|
workspace: Maybe<NotificationWorkspaceType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvitationReviewRequestNotificationBodyType {
|
||||||
|
__typename?: 'InvitationReviewRequestNotificationBodyType';
|
||||||
|
/** The user who created the notification, maybe null when user is deleted or sent by system */
|
||||||
|
createdByUser: Maybe<PublicUserType>;
|
||||||
|
inviteId: Scalars['ID']['output'];
|
||||||
|
/** The type of the notification */
|
||||||
|
type: NotificationType;
|
||||||
|
workspace: Maybe<NotificationWorkspaceType>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface InvitationType {
|
export interface InvitationType {
|
||||||
__typename?: 'InvitationType';
|
__typename?: 'InvitationType';
|
||||||
/** Invitee information */
|
/** Invitee information */
|
||||||
@@ -1501,6 +1530,9 @@ export enum NotificationType {
|
|||||||
InvitationAccepted = 'InvitationAccepted',
|
InvitationAccepted = 'InvitationAccepted',
|
||||||
InvitationBlocked = 'InvitationBlocked',
|
InvitationBlocked = 'InvitationBlocked',
|
||||||
InvitationRejected = 'InvitationRejected',
|
InvitationRejected = 'InvitationRejected',
|
||||||
|
InvitationReviewApproved = 'InvitationReviewApproved',
|
||||||
|
InvitationReviewDeclined = 'InvitationReviewDeclined',
|
||||||
|
InvitationReviewRequest = 'InvitationReviewRequest',
|
||||||
Mention = 'Mention',
|
Mention = 'Mention',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1945,6 +1977,9 @@ export type UnionNotificationBodyType =
|
|||||||
| InvitationAcceptedNotificationBodyType
|
| InvitationAcceptedNotificationBodyType
|
||||||
| InvitationBlockedNotificationBodyType
|
| InvitationBlockedNotificationBodyType
|
||||||
| InvitationNotificationBodyType
|
| InvitationNotificationBodyType
|
||||||
|
| InvitationReviewApprovedNotificationBodyType
|
||||||
|
| InvitationReviewDeclinedNotificationBodyType
|
||||||
|
| InvitationReviewRequestNotificationBodyType
|
||||||
| MentionNotificationBodyType;
|
| MentionNotificationBodyType;
|
||||||
|
|
||||||
export interface UnknownOauthProviderDataType {
|
export interface UnknownOauthProviderDataType {
|
||||||
|
|||||||
Reference in New Issue
Block a user