mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-24 03:26:31 +03:00
feat: send email to owner after member accepted invitation / leave workspace (#4152)
Co-authored-by: DarkSky <darksky2048@gmail.com>
This commit is contained in:
parent
1301605db5
commit
498683ff4c
@ -2,6 +2,7 @@ import { pushNotificationAtom } from '@affine/component/notification-center';
|
|||||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
@ -24,7 +25,11 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
|
|||||||
|
|
||||||
const { jumpToSubPath, jumpToIndex } = useNavigateHelper();
|
const { jumpToSubPath, jumpToIndex } = useNavigateHelper();
|
||||||
const [currentWorkspace] = useCurrentWorkspace();
|
const [currentWorkspace] = useCurrentWorkspace();
|
||||||
|
|
||||||
const workspace = useWorkspace(workspaceId);
|
const workspace = useWorkspace(workspaceId);
|
||||||
|
const [workspaceName] = useBlockSuiteWorkspaceName(
|
||||||
|
workspace.blockSuiteWorkspace
|
||||||
|
);
|
||||||
const workspaces = useAtomValue(rootWorkspacesMetadataAtom);
|
const workspaces = useAtomValue(rootWorkspacesMetadataAtom);
|
||||||
const pushNotification = useSetAtom(pushNotificationAtom);
|
const pushNotification = useSetAtom(pushNotificationAtom);
|
||||||
|
|
||||||
@ -74,13 +79,19 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
|
|||||||
|
|
||||||
const handleLeaveWorkspace = useCallback(async () => {
|
const handleLeaveWorkspace = useCallback(async () => {
|
||||||
closeAndJumpOut();
|
closeAndJumpOut();
|
||||||
await leaveWorkspace(workspaceId);
|
await leaveWorkspace(workspaceId, workspaceName);
|
||||||
|
|
||||||
pushNotification({
|
pushNotification({
|
||||||
title: 'Successfully leave',
|
title: 'Successfully leave',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
}, [closeAndJumpOut, leaveWorkspace, pushNotification, workspaceId]);
|
}, [
|
||||||
|
closeAndJumpOut,
|
||||||
|
leaveWorkspace,
|
||||||
|
pushNotification,
|
||||||
|
workspaceId,
|
||||||
|
workspaceName,
|
||||||
|
]);
|
||||||
|
|
||||||
const onTransformWorkspace = useOnTransformWorkspace();
|
const onTransformWorkspace = useOnTransformWorkspace();
|
||||||
// const handleDelete = useCallback(async () => {
|
// const handleDelete = useCallback(async () => {
|
||||||
|
@ -12,10 +12,13 @@ export function useLeaveWorkspace() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async (workspaceId: string) => {
|
async (workspaceId: string, workspaceName: string) => {
|
||||||
deleteWorkspaceMeta(workspaceId);
|
deleteWorkspaceMeta(workspaceId);
|
||||||
|
|
||||||
await leaveWorkspace({
|
await leaveWorkspace({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
workspaceName,
|
||||||
|
sendLeaveMail: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[deleteWorkspaceMeta, leaveWorkspace]
|
[deleteWorkspaceMeta, leaveWorkspace]
|
||||||
|
@ -35,6 +35,7 @@ export const loader: LoaderFunction = async args => {
|
|||||||
variables: {
|
variables: {
|
||||||
workspaceId: res.getInviteInfo.workspace.id,
|
workspaceId: res.getInviteInfo.workspace.id,
|
||||||
inviteId,
|
inviteId,
|
||||||
|
sendAcceptMail: true,
|
||||||
},
|
},
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
|
|
||||||
|
@ -160,4 +160,50 @@ export class MailService {
|
|||||||
html,
|
html,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async sendAcceptedEmail(
|
||||||
|
to: string,
|
||||||
|
{
|
||||||
|
inviteeName,
|
||||||
|
workspaceName,
|
||||||
|
}: {
|
||||||
|
inviteeName: string;
|
||||||
|
workspaceName: string;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const title = `${inviteeName} accepted your invitation`;
|
||||||
|
|
||||||
|
const html = emailTemplate({
|
||||||
|
title,
|
||||||
|
content: `${inviteeName} has joined ${workspaceName}`,
|
||||||
|
});
|
||||||
|
return this.sendMail({
|
||||||
|
from: this.config.auth.email.sender,
|
||||||
|
to,
|
||||||
|
subject: title,
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async sendLeaveWorkspaceEmail(
|
||||||
|
to: string,
|
||||||
|
{
|
||||||
|
inviteeName,
|
||||||
|
workspaceName,
|
||||||
|
}: {
|
||||||
|
inviteeName: string;
|
||||||
|
workspaceName: string;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const title = `${inviteeName} left ${workspaceName}`;
|
||||||
|
|
||||||
|
const html = emailTemplate({
|
||||||
|
title,
|
||||||
|
content: `${inviteeName} has left your workspace`,
|
||||||
|
});
|
||||||
|
return this.sendMail({
|
||||||
|
from: this.config.auth.email.sender,
|
||||||
|
to,
|
||||||
|
subject: title,
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ export const emailTemplate = ({
|
|||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
buttonContent: string;
|
buttonContent?: string;
|
||||||
buttonUrl: string;
|
buttonUrl?: string;
|
||||||
subContent?: string;
|
subContent?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return `<body style="background: #f6f7fb; overflow: hidden">
|
return `<body style="background: #f6f7fb; overflow: hidden">
|
||||||
@ -59,7 +59,9 @@ export const emailTemplate = ({
|
|||||||
"
|
"
|
||||||
>${content}</td>
|
>${content}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
${
|
||||||
|
buttonContent && buttonUrl
|
||||||
|
? `<tr>
|
||||||
<td style="margin-left: 24px; padding-top: 0; padding-bottom: ${
|
<td style="margin-left: 24px; padding-top: 0; padding-bottom: ${
|
||||||
subContent ? '0' : '64px'
|
subContent ? '0' : '64px'
|
||||||
}">
|
}">
|
||||||
@ -88,7 +90,9 @@ export const emailTemplate = ({
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
${
|
${
|
||||||
subContent
|
subContent
|
||||||
? `<tr>
|
? `<tr>
|
||||||
|
@ -109,6 +109,8 @@ export class InvitationType {
|
|||||||
workspace!: InvitationWorkspaceType;
|
workspace!: InvitationWorkspaceType;
|
||||||
@Field({ description: 'User information' })
|
@Field({ description: 'User information' })
|
||||||
user!: UserType;
|
user!: UserType;
|
||||||
|
@Field({ description: 'Invitee information' })
|
||||||
|
invitee!: UserType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
@ -514,6 +516,17 @@ export class WorkspaceResolver {
|
|||||||
user: true,
|
user: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const invitee = await this.prisma.userWorkspacePermission.findUniqueOrThrow(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
id: inviteId,
|
||||||
|
workspaceId: permission.workspaceId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let avatar = '';
|
let avatar = '';
|
||||||
|
|
||||||
@ -532,6 +545,7 @@ export class WorkspaceResolver {
|
|||||||
id: permission.workspaceId,
|
id: permission.workspaceId,
|
||||||
},
|
},
|
||||||
user: owner.user,
|
user: owner.user,
|
||||||
|
invitee: invitee.user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,8 +564,28 @@ export class WorkspaceResolver {
|
|||||||
@Public()
|
@Public()
|
||||||
async acceptInviteById(
|
async acceptInviteById(
|
||||||
@Args('workspaceId') workspaceId: string,
|
@Args('workspaceId') workspaceId: string,
|
||||||
@Args('inviteId') inviteId: string
|
@Args('inviteId') inviteId: string,
|
||||||
|
@Args('sendAcceptMail', { nullable: true }) sendAcceptMail: boolean
|
||||||
) {
|
) {
|
||||||
|
const {
|
||||||
|
invitee,
|
||||||
|
user: inviter,
|
||||||
|
workspace,
|
||||||
|
} = await this.getInviteInfo(inviteId);
|
||||||
|
|
||||||
|
if (!inviter || !invitee) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
`can not find inviter/invitee by inviteId: ${inviteId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendAcceptMail) {
|
||||||
|
await this.mailer.sendAcceptedEmail(inviter.email, {
|
||||||
|
inviteeName: invitee.name,
|
||||||
|
workspaceName: workspace.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this.permissionProvider.acceptById(workspaceId, inviteId);
|
return this.permissionProvider.acceptById(workspaceId, inviteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,10 +600,35 @@ export class WorkspaceResolver {
|
|||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async leaveWorkspace(
|
async leaveWorkspace(
|
||||||
@CurrentUser() user: UserType,
|
@CurrentUser() user: UserType,
|
||||||
@Args('workspaceId') workspaceId: string
|
@Args('workspaceId') workspaceId: string,
|
||||||
|
@Args('workspaceName') workspaceName: string,
|
||||||
|
@Args('sendLeaveMail', { nullable: true }) sendLeaveMail: boolean
|
||||||
) {
|
) {
|
||||||
await this.permissionProvider.check(workspaceId, user.id);
|
await this.permissionProvider.check(workspaceId, user.id);
|
||||||
|
|
||||||
|
const owner = await this.prisma.userWorkspacePermission.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
type: Permission.Owner,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!owner.user) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
`can not find owner by workspaceId: ${workspaceId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendLeaveMail) {
|
||||||
|
await this.mailer.sendLeaveWorkspaceEmail(owner.user.email, {
|
||||||
|
workspaceName,
|
||||||
|
inviteeName: user.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this.permissionProvider.revoke(workspaceId, user.id);
|
return this.permissionProvider.revoke(workspaceId, user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,9 @@ type InvitationType {
|
|||||||
|
|
||||||
"""User information"""
|
"""User information"""
|
||||||
user: UserType!
|
user: UserType!
|
||||||
|
|
||||||
|
"""Invitee information"""
|
||||||
|
invitee: UserType!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
@ -172,9 +175,9 @@ type Mutation {
|
|||||||
deleteWorkspace(id: String!): Boolean!
|
deleteWorkspace(id: String!): Boolean!
|
||||||
invite(workspaceId: String!, email: String!, permission: Permission!, sendInviteMail: Boolean): String!
|
invite(workspaceId: String!, email: String!, permission: Permission!, sendInviteMail: Boolean): String!
|
||||||
revoke(workspaceId: String!, userId: String!): Boolean!
|
revoke(workspaceId: String!, userId: String!): Boolean!
|
||||||
acceptInviteById(workspaceId: String!, inviteId: String!): Boolean!
|
acceptInviteById(workspaceId: String!, inviteId: String!, sendAcceptMail: Boolean): Boolean!
|
||||||
acceptInvite(workspaceId: String!): Boolean!
|
acceptInvite(workspaceId: String!): Boolean!
|
||||||
leaveWorkspace(workspaceId: String!): Boolean!
|
leaveWorkspace(workspaceId: String!, workspaceName: String!, sendLeaveMail: Boolean): Boolean!
|
||||||
sharePage(workspaceId: String!, pageId: String!): Boolean!
|
sharePage(workspaceId: String!, pageId: String!): Boolean!
|
||||||
revokePage(workspaceId: String!, pageId: String!): Boolean!
|
revokePage(workspaceId: String!, pageId: String!): Boolean!
|
||||||
setBlob(workspaceId: String!, blob: Upload!): String!
|
setBlob(workspaceId: String!, blob: Upload!): String!
|
||||||
|
@ -13,6 +13,7 @@ import { AuthModule } from '../modules/auth';
|
|||||||
import { AuthService } from '../modules/auth/service';
|
import { AuthService } from '../modules/auth/service';
|
||||||
import { PrismaModule } from '../prisma';
|
import { PrismaModule } from '../prisma';
|
||||||
import { RateLimiterModule } from '../throttler';
|
import { RateLimiterModule } from '../throttler';
|
||||||
|
import { getCurrentMailMessageCount, getLatestMailMessage } from './utils';
|
||||||
|
|
||||||
let auth: AuthService;
|
let auth: AuthService;
|
||||||
let module: TestingModule;
|
let module: TestingModule;
|
||||||
@ -68,23 +69,11 @@ test.afterEach(async () => {
|
|||||||
await module.close();
|
await module.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
const getCurrentMailMessageCount = async () => {
|
|
||||||
const response = await fetch('http://localhost:8025/api/v2/messages');
|
|
||||||
const data = await response.json();
|
|
||||||
return data.total;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLatestMailMessage = async () => {
|
|
||||||
const response = await fetch('http://localhost:8025/api/v2/messages');
|
|
||||||
const data = await response.json();
|
|
||||||
return data.items[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
test('should include callbackUrl in sending email', async t => {
|
test('should include callbackUrl in sending email', async t => {
|
||||||
if (skip) {
|
if (skip) {
|
||||||
return t.pass();
|
return t.pass();
|
||||||
}
|
}
|
||||||
await auth.signUp('Alex Yang', 'alexyang@example.org', '123456');
|
// await auth.signUp('Alex Yang', 'alexyang@example.org', '123456');
|
||||||
for (const fn of [
|
for (const fn of [
|
||||||
'sendSetPasswordEmail',
|
'sendSetPasswordEmail',
|
||||||
'sendChangeEmail',
|
'sendChangeEmail',
|
||||||
|
@ -12,6 +12,18 @@ import type { InvitationType, WorkspaceType } from '../modules/workspaces';
|
|||||||
|
|
||||||
const gql = '/graphql';
|
const gql = '/graphql';
|
||||||
|
|
||||||
|
export async function getCurrentMailMessageCount() {
|
||||||
|
const response = await fetch('http://localhost:8025/api/v2/messages');
|
||||||
|
const data = await response.json();
|
||||||
|
return data.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLatestMailMessage() {
|
||||||
|
const response = await fetch('http://localhost:8025/api/v2/messages');
|
||||||
|
const data = await response.json();
|
||||||
|
return data.items[0];
|
||||||
|
}
|
||||||
|
|
||||||
async function signUp(
|
async function signUp(
|
||||||
app: INestApplication,
|
app: INestApplication,
|
||||||
name: string,
|
name: string,
|
||||||
@ -192,7 +204,8 @@ async function inviteUser(
|
|||||||
async function acceptInviteById(
|
async function acceptInviteById(
|
||||||
app: INestApplication,
|
app: INestApplication,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
inviteId: string
|
inviteId: string,
|
||||||
|
sendAcceptMail = false
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.post(gql)
|
.post(gql)
|
||||||
@ -200,7 +213,7 @@ async function acceptInviteById(
|
|||||||
.send({
|
.send({
|
||||||
query: `
|
query: `
|
||||||
mutation {
|
mutation {
|
||||||
acceptInviteById(workspaceId: "${workspaceId}", inviteId: "${inviteId}")
|
acceptInviteById(workspaceId: "${workspaceId}", inviteId: "${inviteId}", sendAcceptMail: ${sendAcceptMail})
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
@ -231,7 +244,8 @@ async function acceptInvite(
|
|||||||
async function leaveWorkspace(
|
async function leaveWorkspace(
|
||||||
app: INestApplication,
|
app: INestApplication,
|
||||||
token: string,
|
token: string,
|
||||||
workspaceId: string
|
workspaceId: string,
|
||||||
|
sendLeaveMail = false
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.post(gql)
|
.post(gql)
|
||||||
@ -240,7 +254,7 @@ async function leaveWorkspace(
|
|||||||
.send({
|
.send({
|
||||||
query: `
|
query: `
|
||||||
mutation {
|
mutation {
|
||||||
leaveWorkspace(workspaceId: "${workspaceId}")
|
leaveWorkspace(workspaceId: "${workspaceId}", workspaceName: "test workspace", sendLeaveMail: ${sendLeaveMail})
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
acceptInvite,
|
acceptInvite,
|
||||||
acceptInviteById,
|
acceptInviteById,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
|
getCurrentMailMessageCount,
|
||||||
|
getLatestMailMessage,
|
||||||
getWorkspace,
|
getWorkspace,
|
||||||
inviteUser,
|
inviteUser,
|
||||||
leaveWorkspace,
|
leaveWorkspace,
|
||||||
@ -100,6 +102,8 @@ test('should leave a workspace', async t => {
|
|||||||
await acceptInvite(app, u2.token.token, workspace.id);
|
await acceptInvite(app, u2.token.token, workspace.id);
|
||||||
|
|
||||||
const leave = await leaveWorkspace(app, u2.token.token, workspace.id);
|
const leave = await leaveWorkspace(app, u2.token.token, workspace.id);
|
||||||
|
|
||||||
|
t.pass();
|
||||||
t.true(leave, 'failed to leave workspace');
|
t.true(leave, 'failed to leave workspace');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,13 +166,15 @@ test('should invite a user by link', async t => {
|
|||||||
t.is(currMember?.inviteId, invite, 'failed to check invite id');
|
t.is(currMember?.inviteId, invite, 'failed to check invite id');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should send invite email', async t => {
|
test('should send email', async t => {
|
||||||
if (mail.hasConfigured()) {
|
if (mail.hasConfigured()) {
|
||||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||||
const u2 = await signUp(app, 'test', 'production@toeverything.info', '1');
|
const u2 = await signUp(app, 'test', 'production@toeverything.info', '1');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app, u1.token.token);
|
const workspace = await createWorkspace(app, u1.token.token);
|
||||||
await inviteUser(
|
const primitiveMailCount = await getCurrentMailMessageCount();
|
||||||
|
|
||||||
|
const invite = await inviteUser(
|
||||||
app,
|
app,
|
||||||
u1.token.token,
|
u1.token.token,
|
||||||
workspace.id,
|
workspace.id,
|
||||||
@ -176,6 +182,60 @@ test('should send invite email', async t => {
|
|||||||
'Admin',
|
'Admin',
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const afterInviteMailCount = await getCurrentMailMessageCount();
|
||||||
|
t.is(
|
||||||
|
primitiveMailCount + 1,
|
||||||
|
afterInviteMailCount,
|
||||||
|
'failed to send invite email'
|
||||||
|
);
|
||||||
|
const inviteEmailContent = await getLatestMailMessage();
|
||||||
|
|
||||||
|
t.not(
|
||||||
|
// @ts-expect-error Third part library type mismatch
|
||||||
|
inviteEmailContent.To.find(item => {
|
||||||
|
return item.Mailbox === 'production';
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
'invite email address was incorrectly sent'
|
||||||
|
);
|
||||||
|
|
||||||
|
const accept = await acceptInviteById(app, workspace.id, invite, true);
|
||||||
|
t.true(accept, 'failed to accept invite');
|
||||||
|
|
||||||
|
const afterAcceptMailCount = await getCurrentMailMessageCount();
|
||||||
|
t.is(
|
||||||
|
afterInviteMailCount + 1,
|
||||||
|
afterAcceptMailCount,
|
||||||
|
'failed to send accepted email to owner'
|
||||||
|
);
|
||||||
|
const acceptEmailContent = await getLatestMailMessage();
|
||||||
|
t.not(
|
||||||
|
// @ts-expect-error Third part library type mismatch
|
||||||
|
acceptEmailContent.To.find(item => {
|
||||||
|
return item.Mailbox === 'u1';
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
'accept email address was incorrectly sent'
|
||||||
|
);
|
||||||
|
|
||||||
|
await leaveWorkspace(app, u2.token.token, workspace.id, true);
|
||||||
|
|
||||||
|
const afterLeaveMailCount = await getCurrentMailMessageCount();
|
||||||
|
t.is(
|
||||||
|
afterAcceptMailCount + 1,
|
||||||
|
afterLeaveMailCount,
|
||||||
|
'failed to send leave email to owner'
|
||||||
|
);
|
||||||
|
const leaveEmailContent = await getLatestMailMessage();
|
||||||
|
t.not(
|
||||||
|
// @ts-expect-error Third part library type mismatch
|
||||||
|
leaveEmailContent.To.find(item => {
|
||||||
|
return item.Mailbox === 'u1';
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
'leave email address was incorrectly sent'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
@ -301,8 +301,12 @@ export const leaveWorkspaceMutation = {
|
|||||||
definitionName: 'leaveWorkspace',
|
definitionName: 'leaveWorkspace',
|
||||||
containsFile: false,
|
containsFile: false,
|
||||||
query: `
|
query: `
|
||||||
mutation leaveWorkspace($workspaceId: String!) {
|
mutation leaveWorkspace($workspaceId: String!, $workspaceName: String!, $sendLeaveMail: Boolean) {
|
||||||
leaveWorkspace(workspaceId: $workspaceId)
|
leaveWorkspace(
|
||||||
|
workspaceId: $workspaceId
|
||||||
|
workspaceName: $workspaceName
|
||||||
|
sendLeaveMail: $sendLeaveMail
|
||||||
|
)
|
||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -475,8 +479,12 @@ export const acceptInviteByInviteIdMutation = {
|
|||||||
definitionName: 'acceptInviteById',
|
definitionName: 'acceptInviteById',
|
||||||
containsFile: false,
|
containsFile: false,
|
||||||
query: `
|
query: `
|
||||||
mutation acceptInviteByInviteId($workspaceId: String!, $inviteId: String!) {
|
mutation acceptInviteByInviteId($workspaceId: String!, $inviteId: String!, $sendAcceptMail: Boolean) {
|
||||||
acceptInviteById(workspaceId: $workspaceId, inviteId: $inviteId)
|
acceptInviteById(
|
||||||
|
workspaceId: $workspaceId
|
||||||
|
inviteId: $inviteId
|
||||||
|
sendAcceptMail: $sendAcceptMail
|
||||||
|
)
|
||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
mutation leaveWorkspace($workspaceId: String!) {
|
mutation leaveWorkspace(
|
||||||
leaveWorkspace(workspaceId: $workspaceId)
|
$workspaceId: String!
|
||||||
|
$workspaceName: String!
|
||||||
|
$sendLeaveMail: Boolean
|
||||||
|
) {
|
||||||
|
leaveWorkspace(
|
||||||
|
workspaceId: $workspaceId
|
||||||
|
workspaceName: $workspaceName
|
||||||
|
sendLeaveMail: $sendLeaveMail
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
mutation acceptInviteByInviteId($workspaceId: String!, $inviteId: String!) {
|
mutation acceptInviteByInviteId(
|
||||||
acceptInviteById(workspaceId: $workspaceId, inviteId: $inviteId)
|
$workspaceId: String!
|
||||||
|
$inviteId: String!
|
||||||
|
$sendAcceptMail: Boolean
|
||||||
|
) {
|
||||||
|
acceptInviteById(
|
||||||
|
workspaceId: $workspaceId
|
||||||
|
inviteId: $inviteId
|
||||||
|
sendAcceptMail: $sendAcceptMail
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -279,6 +279,8 @@ export type GetWorkspacesQuery = {
|
|||||||
|
|
||||||
export type LeaveWorkspaceMutationVariables = Exact<{
|
export type LeaveWorkspaceMutationVariables = Exact<{
|
||||||
workspaceId: Scalars['String']['input'];
|
workspaceId: Scalars['String']['input'];
|
||||||
|
workspaceName: Scalars['String']['input'];
|
||||||
|
sendLeaveMail: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type LeaveWorkspaceMutation = {
|
export type LeaveWorkspaceMutation = {
|
||||||
@ -428,6 +430,7 @@ export type InviteByEmailMutation = { __typename?: 'Mutation'; invite: string };
|
|||||||
export type AcceptInviteByInviteIdMutationVariables = Exact<{
|
export type AcceptInviteByInviteIdMutationVariables = Exact<{
|
||||||
workspaceId: Scalars['String']['input'];
|
workspaceId: Scalars['String']['input'];
|
||||||
inviteId: Scalars['String']['input'];
|
inviteId: Scalars['String']['input'];
|
||||||
|
sendAcceptMail: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type AcceptInviteByInviteIdMutation = {
|
export type AcceptInviteByInviteIdMutation = {
|
||||||
|
@ -12,15 +12,10 @@
|
|||||||
"script": "build"
|
"script": "build"
|
||||||
},
|
},
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{ "runtime": "rustc --version" },
|
||||||
"runtime": "rustc --version"
|
{ "runtime": "node -v" },
|
||||||
},
|
{ "runtime": "clang --version" },
|
||||||
{
|
{ "runtime": "cargo tree" }
|
||||||
"runtime": "node -v"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"runtime": "clang --version"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"outputs": ["{projectRoot}/*.node", "{workspaceRoot}/*.node"]
|
"outputs": ["{projectRoot}/*.node", "{workspaceRoot}/*.node"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user