mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 13:25:12 +00:00
feat(server): role changed email (#9227)
This commit is contained in:
@@ -166,7 +166,7 @@ function generateErrorArgs(name: string, args: ErrorArgs) {
|
||||
|
||||
export function generateUserFriendlyErrors() {
|
||||
const output = [
|
||||
'/* eslint-disable */',
|
||||
'/* oxlint-disable */',
|
||||
'// AUTO GENERATED FILE',
|
||||
`import { createUnionType, Field, ObjectType, registerEnumType } from '@nestjs/graphql';`,
|
||||
'',
|
||||
@@ -374,10 +374,6 @@ export const USER_FRIENDLY_ERRORS = {
|
||||
args: { spaceId: 'string' },
|
||||
message: ({ spaceId }) => `Owner of Space ${spaceId} not found.`,
|
||||
},
|
||||
cant_change_space_owner: {
|
||||
type: 'action_forbidden',
|
||||
message: 'You are not allowed to change the owner of a Space.',
|
||||
},
|
||||
doc_not_found: {
|
||||
type: 'resource_not_found',
|
||||
args: { spaceId: 'string', docId: 'string' },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable */
|
||||
/* oxlint-disable */
|
||||
// AUTO GENERATED FILE
|
||||
import { createUnionType, Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
@@ -240,12 +240,6 @@ export class SpaceOwnerNotFound extends UserFriendlyError {
|
||||
super('internal_server_error', 'space_owner_not_found', message, args);
|
||||
}
|
||||
}
|
||||
|
||||
export class CantChangeSpaceOwner extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
super('action_forbidden', 'cant_change_space_owner', message);
|
||||
}
|
||||
}
|
||||
@ObjectType()
|
||||
class DocNotFoundDataType {
|
||||
@Field() spaceId!: string
|
||||
@@ -630,7 +624,6 @@ export enum ErrorNames {
|
||||
ALREADY_IN_SPACE,
|
||||
SPACE_ACCESS_DENIED,
|
||||
SPACE_OWNER_NOT_FOUND,
|
||||
CANT_CHANGE_SPACE_OWNER,
|
||||
DOC_NOT_FOUND,
|
||||
DOC_ACCESS_DENIED,
|
||||
VERSION_REJECTED,
|
||||
|
||||
@@ -10,6 +10,12 @@ export interface WorkspaceEvents {
|
||||
workspaceId: Workspace['id'];
|
||||
}>;
|
||||
requestApproved: Payload<{ inviteId: string }>;
|
||||
roleChanged: Payload<{
|
||||
userId: User['id'];
|
||||
workspaceId: Workspace['id'];
|
||||
permission: number;
|
||||
}>;
|
||||
ownerTransferred: Payload<{ email: string; workspaceId: Workspace['id'] }>;
|
||||
updated: Payload<{ workspaceId: Workspace['id']; count: number }>;
|
||||
};
|
||||
deleted: Payload<Workspace['id']>;
|
||||
|
||||
@@ -6,7 +6,12 @@ import { URLHelper } from '../helpers';
|
||||
import { metrics } from '../metrics';
|
||||
import type { MailerService, Options } from './mailer';
|
||||
import { MAILER_SERVICE } from './mailer';
|
||||
import { emailTemplate } from './template';
|
||||
import {
|
||||
emailTemplate,
|
||||
getRoleChangedTemplate,
|
||||
type RoleChangedMailParams,
|
||||
} from './template';
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
constructor(
|
||||
@@ -311,4 +316,22 @@ export class MailService {
|
||||
});
|
||||
return this.sendMail({ to, subject: title, html });
|
||||
}
|
||||
|
||||
async sendRoleChangedEmail(to: string, ws: RoleChangedMailParams) {
|
||||
const { subject, title, content } = getRoleChangedTemplate(ws);
|
||||
const html = emailTemplate({ title, content });
|
||||
console.log({ subject, title, content, to });
|
||||
return this.sendMail({ to, subject, html });
|
||||
}
|
||||
|
||||
async sendOwnerTransferred(to: string, ws: { name: string }) {
|
||||
const { name: workspaceName } = ws;
|
||||
const title = `Your ownership of ${workspaceName} has been transferred`;
|
||||
|
||||
const html = emailTemplate({
|
||||
title: 'Ownership transferred',
|
||||
content: `You have transferred ownership of ${workspaceName}. You are now a admin in this workspace.`,
|
||||
});
|
||||
return this.sendMail({ to, subject: title, html });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,3 +219,38 @@ export const emailTemplate = ({
|
||||
</table>
|
||||
</body>`;
|
||||
};
|
||||
|
||||
type RoleChangedMail = {
|
||||
subject: string;
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type RoleChangedMailParams = {
|
||||
name: string;
|
||||
role: 'owner' | 'admin' | 'member' | 'readonly';
|
||||
};
|
||||
|
||||
export const getRoleChangedTemplate = (
|
||||
ws: RoleChangedMailParams
|
||||
): RoleChangedMail => {
|
||||
const { name, role } = ws;
|
||||
let subject = `You are now an ${role} of ${name}`;
|
||||
let title = 'Role update in workspace';
|
||||
let content = `Your role in ${name} has been changed to ${role}. You can continue to collaborate in this workspace.`;
|
||||
|
||||
switch (role) {
|
||||
case 'owner':
|
||||
title = 'Welcome, new workspace owner!';
|
||||
content = `You have been assigned as the owner of ${name}. As a workspace owner, you have full control over this team workspace.`;
|
||||
break;
|
||||
case 'admin':
|
||||
title = `You've been promoted to admin.`;
|
||||
content = `You have been promoted to admin of ${name}. As an admin, you can help the workspace owner manage members in this workspace.`;
|
||||
break;
|
||||
default:
|
||||
subject = `Your role has been changed in ${name}`;
|
||||
break;
|
||||
}
|
||||
return { subject, title, content };
|
||||
};
|
||||
|
||||
@@ -322,10 +322,6 @@ export class PermissionService {
|
||||
this.prisma.workspaceUserPermission.update({
|
||||
where: {
|
||||
workspaceId_userId: { workspaceId: ws, userId: user },
|
||||
// only update permission:
|
||||
// 1. if the new permission is owner and original permission is admin
|
||||
// 2. if the original permission is not owner
|
||||
type: toBeOwner ? Permission.Admin : { not: Permission.Owner },
|
||||
},
|
||||
data: { type: permission },
|
||||
}),
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { getStreamAsBuffer } from 'get-stream';
|
||||
|
||||
import { Cache, MailService } from '../../../base';
|
||||
import { Cache, MailService, UserNotFound } from '../../../base';
|
||||
import { DocContentService } from '../../doc-renderer';
|
||||
import { PermissionService } from '../../permission';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { WorkspaceBlobStorage } from '../../storage';
|
||||
import { UserService } from '../../user';
|
||||
|
||||
@@ -17,6 +17,13 @@ export type InviteInfo = {
|
||||
inviteeUserId?: string;
|
||||
};
|
||||
|
||||
const PermissionToRole = {
|
||||
[Permission.Read]: 'readonly' as const,
|
||||
[Permission.Write]: 'member' as const,
|
||||
[Permission.Admin]: 'admin' as const,
|
||||
[Permission.Owner]: 'owner' as const,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceService {
|
||||
private readonly logger = new Logger(WorkspaceService.name);
|
||||
@@ -78,6 +85,27 @@ export class WorkspaceService {
|
||||
};
|
||||
}
|
||||
|
||||
private async getInviteeEmailTarget(inviteId: string) {
|
||||
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
|
||||
if (!inviteeUserId) {
|
||||
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
|
||||
return;
|
||||
}
|
||||
const workspace = await this.getWorkspaceInfo(workspaceId);
|
||||
const invitee = await this.user.findUserById(inviteeUserId);
|
||||
if (!invitee) {
|
||||
this.logger.error(
|
||||
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
email: invitee.email,
|
||||
workspace,
|
||||
};
|
||||
}
|
||||
|
||||
async sendAcceptedEmail(inviteId: string) {
|
||||
const { workspaceId, inviterUserId, inviteeUserId } =
|
||||
await this.getInviteInfo(inviteId);
|
||||
@@ -167,24 +195,21 @@ export class WorkspaceService {
|
||||
await this.mailer.sendReviewDeclinedEmail(email, { name: workspaceName });
|
||||
}
|
||||
|
||||
private async getInviteeEmailTarget(inviteId: string) {
|
||||
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
|
||||
if (!inviteeUserId) {
|
||||
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
|
||||
return;
|
||||
}
|
||||
const workspace = await this.getWorkspaceInfo(workspaceId);
|
||||
const invitee = await this.user.findUserById(inviteeUserId);
|
||||
if (!invitee) {
|
||||
this.logger.error(
|
||||
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
async sendRoleChangedEmail(
|
||||
userId: string,
|
||||
ws: { id: string; role: Permission }
|
||||
) {
|
||||
const user = await this.user.findUserById(userId);
|
||||
if (!user) throw new UserNotFound();
|
||||
const workspace = await this.getWorkspaceInfo(ws.id);
|
||||
await this.mailer.sendRoleChangedEmail(user?.email, {
|
||||
name: workspace.name,
|
||||
role: PermissionToRole[ws.role],
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
email: invitee.email,
|
||||
workspace,
|
||||
};
|
||||
async sendOwnerTransferred(email: string, ws: { id: string }) {
|
||||
const workspace = await this.getWorkspaceInfo(ws.id);
|
||||
await this.mailer.sendOwnerTransferred(email, { name: workspace.name });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
RequestMutex,
|
||||
TooManyRequest,
|
||||
URLHelper,
|
||||
UserFriendlyError,
|
||||
} from '../../../base';
|
||||
import { CurrentUser } from '../../auth';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
@@ -311,7 +312,17 @@ export class TeamWorkspaceResolver {
|
||||
);
|
||||
|
||||
if (result) {
|
||||
// TODO(@darkskygit): send team role changed mail
|
||||
this.event.emit('workspace.members.roleChanged', {
|
||||
userId,
|
||||
workspaceId,
|
||||
permission,
|
||||
});
|
||||
if (permission === Permission.Owner) {
|
||||
this.event.emit('workspace.members.ownerTransferred', {
|
||||
email: user.email,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -320,6 +331,10 @@ export class TeamWorkspaceResolver {
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error('failed to invite user', e);
|
||||
// pass through user friendly error
|
||||
if (e instanceof UserFriendlyError) {
|
||||
return e;
|
||||
}
|
||||
return new TooManyRequest();
|
||||
}
|
||||
}
|
||||
@@ -353,4 +368,28 @@ export class TeamWorkspaceResolver {
|
||||
// send approve mail
|
||||
await this.workspaceService.sendReviewApproveEmail(inviteId);
|
||||
}
|
||||
|
||||
@OnEvent('workspace.members.roleChanged')
|
||||
async onRoleChanged({
|
||||
userId,
|
||||
workspaceId,
|
||||
permission,
|
||||
}: EventPayload<'workspace.members.roleChanged'>) {
|
||||
// send role changed mail
|
||||
await this.workspaceService.sendRoleChangedEmail(userId, {
|
||||
id: workspaceId,
|
||||
role: permission,
|
||||
});
|
||||
}
|
||||
|
||||
@OnEvent('workspace.members.ownerTransferred')
|
||||
async onOwnerTransferred({
|
||||
email,
|
||||
workspaceId,
|
||||
}: EventPayload<'workspace.members.ownerTransferred'>) {
|
||||
// send role changed mail
|
||||
await this.workspaceService.sendOwnerTransferred(email, {
|
||||
id: workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import type { FileUpload } from '../../../base';
|
||||
import {
|
||||
AlreadyInSpace,
|
||||
Cache,
|
||||
CantChangeSpaceOwner,
|
||||
DocNotFound,
|
||||
EventEmitter,
|
||||
InternalServerError,
|
||||
@@ -383,8 +382,13 @@ export class WorkspaceResolver {
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('workspaceId') workspaceId: string,
|
||||
@Args('email') email: string,
|
||||
@Args('permission', { type: () => Permission }) permission: Permission,
|
||||
@Args('sendInviteMail', { nullable: true }) sendInviteMail: boolean
|
||||
@Args('sendInviteMail', { nullable: true }) sendInviteMail: boolean,
|
||||
@Args('permission', {
|
||||
type: () => Permission,
|
||||
nullable: true,
|
||||
deprecationReason: 'never used',
|
||||
})
|
||||
_permission?: Permission
|
||||
) {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
@@ -392,10 +396,6 @@ export class WorkspaceResolver {
|
||||
Permission.Admin
|
||||
);
|
||||
|
||||
if (permission === Permission.Owner) {
|
||||
throw new CantChangeSpaceOwner();
|
||||
}
|
||||
|
||||
try {
|
||||
// lock to prevent concurrent invite and grant
|
||||
const lockFlag = `invite:${workspaceId}`;
|
||||
@@ -428,7 +428,7 @@ export class WorkspaceResolver {
|
||||
const inviteId = await this.permissions.grant(
|
||||
workspaceId,
|
||||
target.id,
|
||||
permission
|
||||
Permission.Write
|
||||
);
|
||||
if (sendInviteMail) {
|
||||
try {
|
||||
|
||||
@@ -220,7 +220,6 @@ enum ErrorNames {
|
||||
BLOB_QUOTA_EXCEEDED
|
||||
CANNOT_DELETE_ALL_ADMIN_ACCOUNT
|
||||
CANNOT_DELETE_OWN_ACCOUNT
|
||||
CANT_CHANGE_SPACE_OWNER
|
||||
CANT_UPDATE_ONETIME_PAYMENT_SUBSCRIPTION
|
||||
CAPTCHA_VERIFICATION_FAILED
|
||||
COPILOT_ACTION_TAKEN
|
||||
@@ -526,7 +525,7 @@ type Mutation {
|
||||
"""Create a chat session"""
|
||||
forkCopilotSession(options: ForkChatSessionInput!): String!
|
||||
grantMember(permission: Permission!, userId: String!, workspaceId: String!): String!
|
||||
invite(email: String!, permission: Permission!, sendInviteMail: Boolean, workspaceId: String!): String!
|
||||
invite(email: String!, permission: Permission @deprecated(reason: "never used"), sendInviteMail: Boolean, workspaceId: String!): String!
|
||||
inviteBatch(emails: [String!]!, sendInviteMail: Boolean, workspaceId: String!): [InviteResult!]!
|
||||
leaveWorkspace(sendLeaveMail: Boolean, workspaceId: String!, workspaceName: String @deprecated(reason: "no longer used")): Boolean!
|
||||
publishPage(mode: PublicPageMode = Page, pageId: String!, workspaceId: String!): WorkspacePage!
|
||||
|
||||
@@ -147,13 +147,7 @@ test('should create session correctly', async t => {
|
||||
);
|
||||
});
|
||||
|
||||
const inviteId = await inviteUser(
|
||||
app,
|
||||
token,
|
||||
id,
|
||||
'darksky@affine.pro',
|
||||
'Admin'
|
||||
);
|
||||
const inviteId = await inviteUser(app, token, id, 'darksky@affine.pro');
|
||||
await acceptInviteById(app, id, inviteId, false);
|
||||
await assertCreateSession(
|
||||
id,
|
||||
@@ -240,13 +234,7 @@ test('should fork session correctly', async t => {
|
||||
}
|
||||
);
|
||||
|
||||
const inviteId = await inviteUser(
|
||||
app,
|
||||
token,
|
||||
id,
|
||||
'test@affine.pro',
|
||||
'Admin'
|
||||
);
|
||||
const inviteId = await inviteUser(app, token, id, 'test@affine.pro');
|
||||
await acceptInviteById(app, id, inviteId, false);
|
||||
await assertForkSession(
|
||||
newToken,
|
||||
@@ -609,8 +597,7 @@ test('should reject request that user have not permission', async t => {
|
||||
app,
|
||||
anotherToken,
|
||||
workspaceId,
|
||||
'darksky@affine.pro',
|
||||
'Admin'
|
||||
'darksky@affine.pro'
|
||||
);
|
||||
await acceptInviteById(app, workspaceId, inviteId, false);
|
||||
|
||||
|
||||
@@ -44,14 +44,7 @@ test('should send invite email', async t => {
|
||||
|
||||
const stub = Sinon.stub(mail, 'sendMail');
|
||||
|
||||
await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin',
|
||||
true
|
||||
);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, true);
|
||||
|
||||
t.true(stub.calledOnce);
|
||||
|
||||
|
||||
@@ -120,7 +120,6 @@ const init = async (
|
||||
owner.token.token,
|
||||
workspace.id,
|
||||
member.email,
|
||||
permission,
|
||||
shouldSendEmail
|
||||
);
|
||||
await acceptInviteById(app, workspace.id, inviteId, shouldSendEmail);
|
||||
@@ -133,10 +132,16 @@ const init = async (
|
||||
owner.token.token,
|
||||
teamWorkspace.id,
|
||||
member.email,
|
||||
permission,
|
||||
shouldSendEmail
|
||||
);
|
||||
await acceptInviteById(app, teamWorkspace.id, inviteId, shouldSendEmail);
|
||||
await grantMember(
|
||||
app,
|
||||
owner.token.token,
|
||||
teamWorkspace.id,
|
||||
member.id,
|
||||
permission
|
||||
);
|
||||
}
|
||||
|
||||
return member;
|
||||
@@ -437,8 +442,11 @@ test('should be able to manage invite link', async t => {
|
||||
read,
|
||||
} = await init(app, 4);
|
||||
|
||||
for (const workspace of [ws, tws]) {
|
||||
for (const manager of [owner, admin]) {
|
||||
for (const [workspace, managers] of [
|
||||
[ws, [owner]],
|
||||
[tws, [owner, admin]],
|
||||
] as const) {
|
||||
for (const manager of managers) {
|
||||
const { link } = await createInviteLink(
|
||||
app,
|
||||
manager.token.token,
|
||||
@@ -646,16 +654,21 @@ test('should be able to emit events', async t => {
|
||||
const { teamWorkspace: tws, inviteBatch } = await init(app, 4);
|
||||
|
||||
await inviteBatch(['m1@affine.pro', 'm2@affine.pro']);
|
||||
t.true(
|
||||
event.emit.calledOnceWith('workspace.members.updated', {
|
||||
const [membersUpdated] = event.emit
|
||||
.getCalls()
|
||||
.map(call => call.args)
|
||||
.toReversed();
|
||||
t.deepEqual(membersUpdated, [
|
||||
'workspace.members.updated',
|
||||
{
|
||||
workspaceId: tws.id,
|
||||
count: 6,
|
||||
})
|
||||
);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
{
|
||||
const { teamWorkspace: tws, owner, createInviteLink } = await init(app, 10);
|
||||
const { teamWorkspace: tws, owner, createInviteLink } = await init(app);
|
||||
const [, invite] = await createInviteLink(tws);
|
||||
const user = await invite('m3@affine.pro');
|
||||
const { members } = await getWorkspace(app, owner.token.token, tws.id);
|
||||
@@ -679,4 +692,39 @@ test('should be able to emit events', async t => {
|
||||
'should emit review requested event'
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const { teamWorkspace: tws, owner, read } = await init(app);
|
||||
await grantMember(app, owner.token.token, tws.id, read.id, 'Admin');
|
||||
t.deepEqual(
|
||||
event.emit.lastCall.args,
|
||||
[
|
||||
'workspace.members.roleChanged',
|
||||
{ userId: read.id, workspaceId: tws.id, permission: Permission.Admin },
|
||||
],
|
||||
'should emit role changed event'
|
||||
);
|
||||
|
||||
await grantMember(app, owner.token.token, tws.id, read.id, 'Owner');
|
||||
const [ownerTransferred, roleChanged] = event.emit
|
||||
.getCalls()
|
||||
.map(call => call.args)
|
||||
.toReversed();
|
||||
t.deepEqual(
|
||||
roleChanged,
|
||||
[
|
||||
'workspace.members.roleChanged',
|
||||
{ userId: read.id, workspaceId: tws.id, permission: Permission.Owner },
|
||||
],
|
||||
'should emit role changed event'
|
||||
);
|
||||
t.deepEqual(
|
||||
ownerTransferred,
|
||||
[
|
||||
'workspace.members.ownerTransferred',
|
||||
{ email: owner.email, workspaceId: tws.id },
|
||||
],
|
||||
'should emit owner transferred event'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,14 +3,12 @@ import request from 'supertest';
|
||||
|
||||
import type { InvitationType } from '../../src/core/workspaces';
|
||||
import { gql } from './common';
|
||||
import { PermissionEnum } from './utils';
|
||||
|
||||
export async function inviteUser(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
workspaceId: string,
|
||||
email: string,
|
||||
permission: PermissionEnum,
|
||||
sendInviteMail = false
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
@@ -20,7 +18,7 @@ export async function inviteUser(
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
invite(workspaceId: "${workspaceId}", email: "${email}", permission: ${permission}, sendInviteMail: ${sendInviteMail})
|
||||
invite(workspaceId: "${workspaceId}", email: "${email}", sendInviteMail: ${sendInviteMail})
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
@@ -52,13 +52,7 @@ test('should invite a user', async t => {
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const invite = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
const invite = await inviteUser(app, u1.token.token, workspace.id, u2.email);
|
||||
t.truthy(invite, 'failed to invite user');
|
||||
});
|
||||
|
||||
@@ -68,13 +62,7 @@ test('should leave a workspace', async t => {
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
const id = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
const id = await inviteUser(app, u1.token.token, workspace.id, u2.email);
|
||||
await acceptInviteById(app, workspace.id, id, false);
|
||||
|
||||
const leave = await leaveWorkspace(app, u2.token.token, workspace.id);
|
||||
@@ -89,7 +77,7 @@ test('should revoke a user', async t => {
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email);
|
||||
|
||||
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
|
||||
t.is(currWorkspace.members.length, 2, 'failed to invite user');
|
||||
@@ -104,7 +92,7 @@ test('should create user if not exist', async t => {
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
await inviteUser(app, u1.token.token, workspace.id, 'u2@affine.pro', 'Admin');
|
||||
await inviteUser(app, u1.token.token, workspace.id, 'u2@affine.pro');
|
||||
|
||||
const u2 = await user.findUserByEmail('u2@affine.pro');
|
||||
t.not(u2, undefined, 'failed to create user');
|
||||
@@ -118,24 +106,12 @@ test('should invite a user by link', async t => {
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const invite = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
const invite = await inviteUser(app, u1.token.token, workspace.id, u2.email);
|
||||
|
||||
const accept = await acceptInviteById(app, workspace.id, invite);
|
||||
t.true(accept, 'failed to accept invite');
|
||||
|
||||
const invite1 = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
const invite1 = await inviteUser(app, u1.token.token, workspace.id, u2.email);
|
||||
|
||||
t.is(invite, invite1, 'repeat the invitation must return same id');
|
||||
|
||||
@@ -159,7 +135,6 @@ test('should send email', async t => {
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin',
|
||||
true
|
||||
);
|
||||
|
||||
@@ -224,20 +199,8 @@ test('should support pagination for member', async t => {
|
||||
const u3 = await signUp(app, 'u3', 'u3@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
const invite1 = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
const invite2 = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u3.email,
|
||||
'Admin'
|
||||
);
|
||||
const invite1 = await inviteUser(app, u1.token.token, workspace.id, u2.email);
|
||||
const invite2 = await inviteUser(app, u1.token.token, workspace.id, u3.email);
|
||||
|
||||
await acceptInviteById(app, workspace.id, invite1, false);
|
||||
await acceptInviteById(app, workspace.id, invite2, false);
|
||||
@@ -267,13 +230,7 @@ test('should limit member count correctly', async t => {
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await Promise.allSettled(
|
||||
Array.from({ length: 10 }).map(async (_, i) =>
|
||||
inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
`u${i}@affine.pro`,
|
||||
'Admin'
|
||||
)
|
||||
inviteUser(app, u1.token.token, workspace.id, `u${i}@affine.pro`)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ test('should share a page', async t => {
|
||||
await acceptInviteById(
|
||||
app,
|
||||
workspace.id,
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin')
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email)
|
||||
);
|
||||
const invited = await publishPage(app, u2.token.token, workspace.id, 'page2');
|
||||
t.is(invited.id, 'page2', 'failed to share page');
|
||||
@@ -211,7 +211,7 @@ test('should can get workspace doc', async t => {
|
||||
await acceptInviteById(
|
||||
app,
|
||||
workspace.id,
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin')
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email)
|
||||
);
|
||||
|
||||
const res2 = await request(app.getHttpServer())
|
||||
|
||||
Reference in New Issue
Block a user